mirror of
https://github.com/BishopFox/sliver.git
synced 2026-11-09 01:49:40 -10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2ea95543a9 |
No files matched your search
+13
-95
@@ -34,8 +34,6 @@ package command
|
||||
*/
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/bishopfox/sliver/client/command/backdoor"
|
||||
"github.com/bishopfox/sliver/client/command/environment"
|
||||
"github.com/bishopfox/sliver/client/command/exec"
|
||||
@@ -53,7 +51,6 @@ import (
|
||||
"github.com/bishopfox/sliver/client/command/portfwd"
|
||||
"github.com/bishopfox/sliver/client/command/privilege"
|
||||
"github.com/bishopfox/sliver/client/command/processes"
|
||||
"github.com/bishopfox/sliver/client/command/reaction"
|
||||
"github.com/bishopfox/sliver/client/command/registry"
|
||||
"github.com/bishopfox/sliver/client/command/screenshot"
|
||||
"github.com/bishopfox/sliver/client/command/sessions"
|
||||
@@ -74,13 +71,6 @@ const (
|
||||
// BindCommands - Bind commands to a App
|
||||
func BindCommands(con *console.SliverConsoleClient) {
|
||||
|
||||
n, err := reaction.LoadReactions()
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
con.PrintErrorf("Failed to load reactions: %s\n", err)
|
||||
} else if n > 0 {
|
||||
con.PrintInfof("Loaded %d reaction(s) from disk\n", n)
|
||||
}
|
||||
|
||||
con.App.SetPrintHelp(help.HelpCmd(con)) // Responsible for display long-form help templates, etc.
|
||||
|
||||
// [ Update ] --------------------------------------------------------------
|
||||
@@ -1539,10 +1529,10 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
|
||||
// [ Environment ] ---------------------------------------------
|
||||
|
||||
envCmd := &grumble.Command{
|
||||
Name: consts.EnvStr,
|
||||
con.App.AddCommand(&grumble.Command{
|
||||
Name: consts.GetEnvStr,
|
||||
Help: "List environment variables",
|
||||
LongHelp: help.GetHelpFor([]string{consts.EnvStr}),
|
||||
LongHelp: help.GetHelpFor([]string{consts.GetEnvStr}),
|
||||
Flags: func(f *grumble.Flags) {
|
||||
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
|
||||
},
|
||||
@@ -1556,11 +1546,12 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
return nil
|
||||
},
|
||||
HelpGroup: consts.GenericHelpGroup,
|
||||
}
|
||||
envCmd.AddCommand(&grumble.Command{
|
||||
Name: consts.SetStr,
|
||||
})
|
||||
|
||||
con.App.AddCommand(&grumble.Command{
|
||||
Name: consts.SetEnvStr,
|
||||
Help: "Set environment variables",
|
||||
LongHelp: help.GetHelpFor([]string{consts.EnvStr, consts.SetStr}),
|
||||
LongHelp: help.GetHelpFor([]string{consts.SetEnvStr}),
|
||||
Flags: func(f *grumble.Flags) {
|
||||
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
|
||||
},
|
||||
@@ -1576,10 +1567,11 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
},
|
||||
HelpGroup: consts.GenericHelpGroup,
|
||||
})
|
||||
envCmd.AddCommand(&grumble.Command{
|
||||
Name: consts.UnsetStr,
|
||||
|
||||
con.App.AddCommand(&grumble.Command{
|
||||
Name: consts.UnsetEnvStr,
|
||||
Help: "Clear environment variables",
|
||||
LongHelp: help.GetHelpFor([]string{consts.EnvStr, consts.UnsetStr}),
|
||||
LongHelp: help.GetHelpFor([]string{consts.UnsetEnvStr}),
|
||||
Args: func(a *grumble.Args) {
|
||||
a.String("name", "environment variable name")
|
||||
},
|
||||
@@ -1594,9 +1586,6 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
},
|
||||
HelpGroup: consts.GenericHelpGroup,
|
||||
})
|
||||
con.App.AddCommand(envCmd)
|
||||
|
||||
// [ Licenses ] ---------------------------------------------
|
||||
|
||||
con.App.AddCommand(&grumble.Command{
|
||||
Name: consts.LicensesStr,
|
||||
@@ -1622,6 +1611,7 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
},
|
||||
HelpGroup: consts.SliverWinHelpGroup,
|
||||
}
|
||||
|
||||
registryCmd.AddCommand(&grumble.Command{
|
||||
Name: consts.RegistryReadStr,
|
||||
Help: "Read values from the Windows registry",
|
||||
@@ -1856,7 +1846,6 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
con.App.AddCommand(wgSocksCmd)
|
||||
|
||||
// [ Portfwd ] --------------------------------------------------------------
|
||||
|
||||
portfwdCmd := &grumble.Command{
|
||||
Name: consts.PortfwdStr,
|
||||
Help: "In-band TCP port forwarding",
|
||||
@@ -1908,7 +1897,6 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
con.App.AddCommand(portfwdCmd)
|
||||
|
||||
// [ Monitor ] --------------------------------------------------------------
|
||||
|
||||
monitorCmd := &grumble.Command{
|
||||
Name: consts.MonitorStr,
|
||||
Help: "Monitor threat intel platforms for Sliver implants",
|
||||
@@ -2066,74 +2054,4 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
HelpGroup: consts.GenericHelpGroup,
|
||||
})
|
||||
con.App.AddCommand(lootCmd)
|
||||
|
||||
// [ Reactions ] -----------------------------------------------------------------
|
||||
|
||||
reactionCmd := &grumble.Command{
|
||||
Name: consts.ReactionStr,
|
||||
Help: "Manage automatic reactions to events",
|
||||
LongHelp: help.GetHelpFor([]string{consts.ReactionStr}),
|
||||
Run: func(ctx *grumble.Context) error {
|
||||
con.Println()
|
||||
reaction.ReactionCmd(ctx, con)
|
||||
con.Println()
|
||||
return nil
|
||||
},
|
||||
HelpGroup: consts.GenericHelpGroup,
|
||||
}
|
||||
reactionCmd.AddCommand(&grumble.Command{
|
||||
Name: consts.SetStr,
|
||||
Help: "Set a reaction to an event",
|
||||
LongHelp: help.GetHelpFor([]string{consts.ReactionStr, consts.SetStr}),
|
||||
Flags: func(f *grumble.Flags) {
|
||||
f.String("e", "event", "", "specify the event type to react to")
|
||||
},
|
||||
Run: func(ctx *grumble.Context) error {
|
||||
con.Println()
|
||||
reaction.ReactionSetCmd(ctx, con)
|
||||
con.Println()
|
||||
return nil
|
||||
},
|
||||
HelpGroup: consts.GenericHelpGroup,
|
||||
})
|
||||
reactionCmd.AddCommand(&grumble.Command{
|
||||
Name: consts.UnsetStr,
|
||||
Help: "Unset an existing reaction",
|
||||
LongHelp: help.GetHelpFor([]string{consts.ReactionStr, consts.UnsetStr}),
|
||||
Flags: func(f *grumble.Flags) {
|
||||
f.Int("i", "id", 0, "the id of the reaction to remove")
|
||||
},
|
||||
Run: func(ctx *grumble.Context) error {
|
||||
con.Println()
|
||||
reaction.ReactionUnsetCmd(ctx, con)
|
||||
con.Println()
|
||||
return nil
|
||||
},
|
||||
HelpGroup: consts.GenericHelpGroup,
|
||||
})
|
||||
reactionCmd.AddCommand(&grumble.Command{
|
||||
Name: consts.SaveStr,
|
||||
Help: "Save current reactions to disk",
|
||||
LongHelp: help.GetHelpFor([]string{consts.ReactionStr, consts.SaveStr}),
|
||||
Run: func(ctx *grumble.Context) error {
|
||||
con.Println()
|
||||
reaction.ReactionSaveCmd(ctx, con)
|
||||
con.Println()
|
||||
return nil
|
||||
},
|
||||
HelpGroup: consts.GenericHelpGroup,
|
||||
})
|
||||
reactionCmd.AddCommand(&grumble.Command{
|
||||
Name: consts.ReloadStr,
|
||||
Help: "Reload reactions from disk, replaces the running configuration",
|
||||
LongHelp: help.GetHelpFor([]string{consts.ReactionStr, consts.ReloadStr}),
|
||||
Run: func(ctx *grumble.Context) error {
|
||||
con.Println()
|
||||
reaction.ReactionReloadCmd(ctx, con)
|
||||
con.Println()
|
||||
return nil
|
||||
},
|
||||
HelpGroup: consts.GenericHelpGroup,
|
||||
})
|
||||
con.App.AddCommand(reactionCmd)
|
||||
}
|
||||
@@ -32,7 +32,7 @@ func MigrateCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
}
|
||||
|
||||
pid := ctx.Args.Uint("pid")
|
||||
config := con.GetActiveSessionConfig()
|
||||
config := con.GetActiveSliverConfig()
|
||||
ctrl := make(chan bool)
|
||||
con.SpinUntil(fmt.Sprintf("Migrating into %d ...", pid), ctrl)
|
||||
migrate, err := con.Rpc.Migrate(context.Background(), &clientpb.MigrateReq{
|
||||
|
||||
@@ -19,9 +19,9 @@ package filesystem
|
||||
*/
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
|
||||
@@ -48,17 +48,16 @@ func LsCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
if err != nil {
|
||||
con.PrintWarnf("%s\n", err)
|
||||
} else {
|
||||
PrintLs(ls, con)
|
||||
PrintLs(con.App.Stdout(), ls)
|
||||
}
|
||||
}
|
||||
|
||||
// PrintLs - Display an sliverpb.Ls object
|
||||
func PrintLs(ls *sliverpb.Ls, con *console.SliverConsoleClient) {
|
||||
con.Printf("%s\n", ls.Path)
|
||||
con.Printf("%s\n", strings.Repeat("=", len(ls.Path)))
|
||||
func PrintLs(stdout io.Writer, ls *sliverpb.Ls) {
|
||||
fmt.Fprintf(stdout, "%s\n", ls.Path)
|
||||
fmt.Fprintf(stdout, "%s\n", strings.Repeat("=", len(ls.Path)))
|
||||
|
||||
outputBuf := bytes.NewBufferString("")
|
||||
table := tabwriter.NewWriter(outputBuf, 0, 2, 2, ' ', 0)
|
||||
table := tabwriter.NewWriter(stdout, 0, 2, 2, ' ', 0)
|
||||
for _, fileInfo := range ls.Files {
|
||||
if fileInfo.IsDir {
|
||||
fmt.Fprintf(table, "%s\t<dir>\t\n", fileInfo.Name)
|
||||
@@ -67,5 +66,4 @@ func PrintLs(ls *sliverpb.Ls, con *console.SliverConsoleClient) {
|
||||
}
|
||||
}
|
||||
table.Flush()
|
||||
con.Printf("%s\n", outputBuf.String())
|
||||
}
|
||||
@@ -24,17 +24,12 @@ package help
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
consts "github.com/bishopfox/sliver/client/constants"
|
||||
)
|
||||
|
||||
const (
|
||||
sep = "."
|
||||
)
|
||||
|
||||
var (
|
||||
|
||||
// NOTE: For sub-commands use a "." hierarchy, for example root.sub for "sub" help
|
||||
@@ -76,30 +71,25 @@ var (
|
||||
consts.BackdoorStr: backdoorHelp,
|
||||
consts.SpawnDllStr: spawnDllHelp,
|
||||
|
||||
consts.WebsitesStr: websitesHelp,
|
||||
consts.ScreenshotStr: screenshotHelp,
|
||||
consts.MakeTokenStr: makeTokenHelp,
|
||||
consts.EnvStr: getEnvHelp,
|
||||
consts.EnvStr + sep + consts.SetStr: setEnvHelp,
|
||||
consts.RegistryWriteStr: regWriteHelp,
|
||||
consts.RegistryReadStr: regReadHelp,
|
||||
consts.RegistryCreateKeyStr: regCreateKeyHelp,
|
||||
consts.PivotsListStr: pivotListHelp,
|
||||
consts.WgPortFwdStr: wgPortFwdHelp,
|
||||
consts.WgSocksStr: wgSocksHelp,
|
||||
consts.SSHStr: sshHelp,
|
||||
consts.WebsitesStr: websitesHelp,
|
||||
consts.ScreenshotStr: screenshotHelp,
|
||||
consts.MakeTokenStr: makeTokenHelp,
|
||||
consts.GetEnvStr: getEnvHelp,
|
||||
consts.SetEnvStr: setEnvHelp,
|
||||
consts.RegistryWriteStr: regWriteHelp,
|
||||
consts.RegistryReadStr: regReadHelp,
|
||||
consts.RegistryCreateKeyStr: regCreateKeyHelp,
|
||||
consts.PivotsListStr: pivotListHelp,
|
||||
consts.WgPortFwdStr: wgPortFwdHelp,
|
||||
consts.WgSocksStr: wgSocksHelp,
|
||||
consts.SSHStr: sshHelp,
|
||||
|
||||
// Loot
|
||||
consts.LootStr: lootHelp,
|
||||
|
||||
// Profiles
|
||||
consts.ProfilesStr + sep + consts.NewStr: newProfileHelp,
|
||||
consts.ProfilesStr + sep + consts.GenerateStr: generateProfileHelp,
|
||||
|
||||
// Reactions
|
||||
consts.ReactionStr: reactionHelp,
|
||||
consts.ReactionStr + sep + consts.SetStr: reactionSetHelp,
|
||||
consts.ReactionStr + sep + consts.UnsetStr: reactionUnsetHelp,
|
||||
consts.ProfilesStr + "." + consts.NewStr: newProfileHelp,
|
||||
consts.ProfilesStr + "." + consts.GenerateStr: generateProfileHelp,
|
||||
}
|
||||
|
||||
jobsHelp = `[[.Bold]]Command:[[.Normal]] jobs <options>
|
||||
@@ -554,60 +544,6 @@ loot --filter creds
|
||||
|
||||
# Display the contents of a piece of loot:
|
||||
loot fetch
|
||||
`
|
||||
|
||||
reactionHelp = fmt.Sprintf(`[[.Bold]]Command:[[.Normal]] reaction
|
||||
[[.Bold]]About:[[.Normal]] Automate commands in reaction to event(s).
|
||||
|
||||
[[.Bold]]Reactable Events:[[.Normal]]
|
||||
% 20s Triggered when a new session is opened to a target
|
||||
% 20s Triggered on changes to session metadata
|
||||
% 20s Triggered when a session is closed (for any reason)
|
||||
% 20s Triggered when a canary is burned or created
|
||||
% 20s Triggered when implants are discovered on threat intel platforms
|
||||
% 20s Triggered when a new piece of loot is added to the server
|
||||
% 20s Triggered when a piece of loot is removed from the server
|
||||
`,
|
||||
consts.SessionOpenedEvent,
|
||||
consts.SessionUpdateEvent,
|
||||
consts.SessionClosedEvent,
|
||||
consts.CanaryEvent,
|
||||
consts.WatchtowerEvent,
|
||||
consts.LootAddedEvent,
|
||||
consts.LootRemovedEvent,
|
||||
)
|
||||
|
||||
reactionSetHelp = fmt.Sprintf(`[[.Bold]]Command:[[.Normal]] reaction set
|
||||
[[.Bold]]About:[[.Normal]] Set automated commands in reaction to event(s).
|
||||
|
||||
[[.Bold]]Examples:[[.Normal]]
|
||||
# The command uses interactive menus to build a reaction. Simply run:
|
||||
reaction set
|
||||
|
||||
[[.Bold]]Reactable Events:[[.Normal]]
|
||||
% 20s Triggered when a new session is opened to a target
|
||||
% 20s Triggered on changes to session metadata
|
||||
% 20s Triggered when a session is closed (for any reason)
|
||||
% 20s Triggered when a canary is burned or created
|
||||
% 20s Triggered when implants are discovered on threat intel platforms
|
||||
% 20s Triggered when a new piece of loot is added to the server
|
||||
% 20s Triggered when a piece of loot is removed from the server
|
||||
`,
|
||||
consts.SessionOpenedEvent,
|
||||
consts.SessionUpdateEvent,
|
||||
consts.SessionClosedEvent,
|
||||
consts.CanaryEvent,
|
||||
consts.WatchtowerEvent,
|
||||
consts.LootAddedEvent,
|
||||
consts.LootRemovedEvent,
|
||||
)
|
||||
|
||||
reactionUnsetHelp = `[[.Bold]]Command:[[.Normal]] reaction unset
|
||||
[[.Bold]]About:[[.Normal]] Unset/remove automated commands in reaction to event(s).
|
||||
|
||||
[[.Bold]]Examples:[[.Normal]]
|
||||
# Remove a reaction
|
||||
reaction unset --id 1
|
||||
`
|
||||
)
|
||||
|
||||
@@ -632,7 +568,7 @@ const (
|
||||
// GetHelpFor - Get help string for a command
|
||||
func GetHelpFor(cmdName []string) string {
|
||||
if 0 < len(cmdName) {
|
||||
if helpTmpl, ok := cmdHelp[strings.Join(cmdName, sep)]; ok {
|
||||
if helpTmpl, ok := cmdHelp[strings.Join(cmdName, ".")]; ok {
|
||||
return FormatHelpTmpl(helpTmpl)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ func StageListenerCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
}
|
||||
|
||||
profile := generate.GetImplantProfileByName(profileName, con)
|
||||
if profile != nil {
|
||||
if profile == nil {
|
||||
con.PrintErrorf("Profile not found\n")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ func GetSystemCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
return
|
||||
}
|
||||
process := ctx.Flags.String("process")
|
||||
config := con.GetActiveSessionConfig()
|
||||
config := con.GetActiveSliverConfig()
|
||||
ctrl := make(chan bool)
|
||||
con.SpinUntil("Attempting to create a new sliver session as 'NT AUTHORITY\\SYSTEM'...", ctrl)
|
||||
|
||||
|
||||
@@ -1,23 +1,5 @@
|
||||
package processes
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2021 Bishop Fox
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
@@ -1,23 +1,5 @@
|
||||
package processes
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2021 Bishop Fox
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
Reaction
|
||||
=========
|
||||
|
||||
Commands to set a reaction, or unset a reaction. Reactions allow the operator to automate commands in response to event(s).
|
||||
|
||||
@@ -1,80 +0,0 @@
|
||||
package reaction
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2021 Bishop Fox
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"path"
|
||||
|
||||
"github.com/bishopfox/sliver/client/assets"
|
||||
"github.com/bishopfox/sliver/client/core"
|
||||
)
|
||||
|
||||
const (
|
||||
ReactionFileName = "reactions.json"
|
||||
)
|
||||
|
||||
// GetReactionFilePath - Get the
|
||||
func GetReactionFilePath() string {
|
||||
return path.Join(assets.GetRootAppDir(), ReactionFileName)
|
||||
}
|
||||
|
||||
// SaveReactions - Save the reactions to the reaction file
|
||||
func SaveReactions(reactions []core.Reaction) error {
|
||||
reactionFilePath := GetReactionFilePath()
|
||||
data, err := json.MarshalIndent(reactions, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ioutil.WriteFile(reactionFilePath, data, 0600)
|
||||
}
|
||||
|
||||
// LoadReactions - Save the reactions to the reaction file
|
||||
func LoadReactions() (int, error) {
|
||||
reactionFilePath := GetReactionFilePath()
|
||||
data, err := ioutil.ReadFile(reactionFilePath)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
reactions := []core.Reaction{}
|
||||
err = json.Unmarshal(data, &reactions)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
for _, oldReaction := range core.Reactions.All() {
|
||||
core.Reactions.Remove(oldReaction.ID)
|
||||
}
|
||||
for _, reaction := range reactions {
|
||||
if !isReactable(reaction) {
|
||||
continue
|
||||
}
|
||||
core.Reactions.Add(reaction)
|
||||
}
|
||||
return len(reactions), nil
|
||||
}
|
||||
|
||||
func isReactable(reaction core.Reaction) bool {
|
||||
for _, eventType := range core.ReactableEvents {
|
||||
if reaction.EventType == eventType {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
package reaction
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2021 Bishop Fox
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
consts "github.com/bishopfox/sliver/client/constants"
|
||||
"github.com/bishopfox/sliver/client/core"
|
||||
"github.com/desertbit/grumble"
|
||||
)
|
||||
|
||||
// ReactionCmd - Manage reactions to events
|
||||
func ReactionCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
totalReactions := 0
|
||||
for _, eventType := range core.ReactableEvents {
|
||||
reactions := core.Reactions.On(eventType)
|
||||
if 0 < len(reactions) {
|
||||
displayReactionsTable(eventType, reactions, con)
|
||||
}
|
||||
totalReactions += len(reactions)
|
||||
}
|
||||
if totalReactions == 0 {
|
||||
con.PrintInfof("No reactions set\n")
|
||||
}
|
||||
}
|
||||
|
||||
func displayReactionsTable(eventType string, reactions []core.Reaction, con *console.SliverConsoleClient) {
|
||||
|
||||
// Title
|
||||
con.Printf("%s%s%s\n", console.Bold, EventTypeToTitle(eventType), console.Normal)
|
||||
|
||||
// Table
|
||||
outputBuf := bytes.NewBufferString("")
|
||||
table := tabwriter.NewWriter(outputBuf, 0, 2, 2, ' ', 0)
|
||||
fmt.Fprintf(table, "ID\tCommands\t\n")
|
||||
fmt.Fprintf(table, "%s\t%s\t\n",
|
||||
strings.Repeat("=", len("ID")),
|
||||
strings.Repeat("=", len("Commands")),
|
||||
)
|
||||
for _, react := range reactions {
|
||||
fmt.Fprintf(table, "%d\t%s\t\n",
|
||||
react.ID, strings.Join(react.Commands, ","),
|
||||
)
|
||||
}
|
||||
table.Flush()
|
||||
con.Printf("%s", outputBuf.String())
|
||||
}
|
||||
|
||||
// EventTypeToTitle - Convert an eventType to a more human friendly string
|
||||
func EventTypeToTitle(eventType string) string {
|
||||
switch eventType {
|
||||
|
||||
case consts.SessionOpenedEvent:
|
||||
return "Session Opened"
|
||||
case consts.SessionClosedEvent:
|
||||
return "Session Closed"
|
||||
case consts.SessionUpdateEvent:
|
||||
return "Session Updated"
|
||||
|
||||
case consts.CanaryEvent:
|
||||
return "Canary Trigger"
|
||||
|
||||
case consts.WatchtowerEvent:
|
||||
return "Watchtower Trigger"
|
||||
|
||||
default:
|
||||
return eventType
|
||||
}
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
package reaction
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2021 Bishop Fox
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/desertbit/grumble"
|
||||
)
|
||||
|
||||
// ReactionSaveCmd - Manage reactions to events
|
||||
func ReactionReloadCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
if _, err := os.Stat(GetReactionFilePath()); os.IsNotExist(err) {
|
||||
con.PrintWarnf("Missing reaction file %s\n", GetReactionFilePath())
|
||||
return
|
||||
}
|
||||
confirm := false
|
||||
prompt := &survey.Confirm{Message: "Reload reactions from disk?"}
|
||||
survey.AskOne(prompt, &confirm)
|
||||
if !confirm {
|
||||
return
|
||||
}
|
||||
|
||||
n, err := LoadReactions()
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to load reactions: %s\n", err)
|
||||
}
|
||||
con.Println()
|
||||
con.PrintInfof("Reloaded %d reactions from disk\n", n)
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
package reaction
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2021 Bishop Fox
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/client/core"
|
||||
"github.com/desertbit/grumble"
|
||||
)
|
||||
|
||||
// ReactionSaveCmd - Manage reactions to events
|
||||
func ReactionSaveCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
if _, err := os.Stat(GetReactionFilePath()); !os.IsNotExist(err) {
|
||||
confirm := false
|
||||
prompt := &survey.Confirm{Message: "Overwrite reactions on disk?"}
|
||||
survey.AskOne(prompt, &confirm)
|
||||
if !confirm {
|
||||
return
|
||||
}
|
||||
}
|
||||
err := SaveReactions(core.Reactions.All())
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
} else {
|
||||
con.PrintInfof("Saved reactions to disk\n")
|
||||
}
|
||||
}
|
||||
@@ -1,106 +0,0 @@
|
||||
package reaction
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2021 Bishop Fox
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/client/core"
|
||||
"github.com/desertbit/grumble"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrNonReactableEvent - Event does not exist or is not supported by reactions
|
||||
ErrNonReactableEvent = errors.New("non-reactable event type")
|
||||
)
|
||||
|
||||
// ReactionSetCmd - Set a reaction upon an event
|
||||
func ReactionSetCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
eventType, err := getEventType(ctx, con)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
con.Println()
|
||||
con.PrintInfof("Setting reaction to: %s\n", EventTypeToTitle(eventType))
|
||||
con.Println()
|
||||
rawCommands, err := userCommands()
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
commands := []string{}
|
||||
for _, rawCommand := range strings.Split(rawCommands, "\n") {
|
||||
if rawCommand != "" {
|
||||
commands = append(commands, rawCommand)
|
||||
}
|
||||
}
|
||||
|
||||
reaction := core.Reactions.Add(core.Reaction{
|
||||
EventType: eventType,
|
||||
Commands: commands,
|
||||
})
|
||||
|
||||
con.Println()
|
||||
con.PrintInfof("Set reaction to %s (id: %d)\n", eventType, reaction.ID)
|
||||
}
|
||||
|
||||
func getEventType(ctx *grumble.Context, con *console.SliverConsoleClient) (string, error) {
|
||||
rawEventType := ctx.Flags.String("event")
|
||||
if rawEventType == "" {
|
||||
return selectEventType(con)
|
||||
} else {
|
||||
for _, eventType := range core.ReactableEvents {
|
||||
if eventType == rawEventType {
|
||||
return eventType, nil
|
||||
}
|
||||
}
|
||||
return "", ErrNonReactableEvent
|
||||
}
|
||||
}
|
||||
|
||||
func selectEventType(con *console.SliverConsoleClient) (string, error) {
|
||||
prompt := &survey.Select{
|
||||
Message: "Select an event:",
|
||||
Options: core.ReactableEvents,
|
||||
}
|
||||
selection := ""
|
||||
err := survey.AskOne(prompt, &selection)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
for _, eventType := range core.ReactableEvents {
|
||||
if eventType == selection {
|
||||
return eventType, nil
|
||||
}
|
||||
}
|
||||
return "", ErrNonReactableEvent
|
||||
}
|
||||
|
||||
func userCommands() (string, error) {
|
||||
text := ""
|
||||
prompt := &survey.Multiline{
|
||||
Message: "Enter commands: ",
|
||||
}
|
||||
err := survey.AskOne(prompt, &text)
|
||||
return text, err
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
package reaction
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2021 Bishop Fox
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/client/core"
|
||||
"github.com/desertbit/grumble"
|
||||
)
|
||||
|
||||
// ReactionUnsetCmd - Unset a reaction upon an event
|
||||
func ReactionUnsetCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
reactionID := ctx.Flags.Int("id")
|
||||
if reactionID == 0 {
|
||||
reaction, err := selectReaction(con)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
reactionID = reaction.ID
|
||||
}
|
||||
success := core.Reactions.Remove(reactionID)
|
||||
if success {
|
||||
con.Println()
|
||||
con.PrintInfof("Successfully removed reaction with id %d", reactionID)
|
||||
} else {
|
||||
con.PrintWarnf("No reaction found with id %d", reactionID)
|
||||
}
|
||||
con.Println()
|
||||
}
|
||||
|
||||
func selectReaction(con *console.SliverConsoleClient) (*core.Reaction, error) {
|
||||
outputBuf := bytes.NewBufferString("")
|
||||
table := tabwriter.NewWriter(outputBuf, 0, 2, 2, ' ', 0)
|
||||
allReactions := core.Reactions.All()
|
||||
for _, react := range allReactions {
|
||||
fmt.Fprintf(table, "%d\t%s\t%s\t\n",
|
||||
react.ID, react.EventType, strings.Join(react.Commands, ", "),
|
||||
)
|
||||
}
|
||||
table.Flush()
|
||||
options := strings.Split(outputBuf.String(), "\n")
|
||||
options = options[:len(options)-1] // Remove blank line at the end
|
||||
|
||||
// Prompt user with options
|
||||
prompt := &survey.Select{
|
||||
Message: "Select a reaction:",
|
||||
Options: options,
|
||||
}
|
||||
selection := ""
|
||||
err := survey.AskOne(prompt, &selection)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for index, option := range options {
|
||||
if option == selection {
|
||||
return &allReactions[index], nil
|
||||
}
|
||||
}
|
||||
return nil, errors.New("reaction not found")
|
||||
}
|
||||
@@ -41,7 +41,6 @@ import (
|
||||
|
||||
"time"
|
||||
|
||||
"github.com/desertbit/go-shlex"
|
||||
"github.com/desertbit/grumble"
|
||||
"github.com/fatih/color"
|
||||
)
|
||||
@@ -204,47 +203,11 @@ func (con *SliverConsoleClient) EventLoop() {
|
||||
con.Println()
|
||||
}
|
||||
|
||||
con.triggerReactions(event)
|
||||
|
||||
con.Printf(Clearln + con.GetPrompt())
|
||||
bufio.NewWriter(con.App.Stdout()).Flush()
|
||||
}
|
||||
}
|
||||
|
||||
func (con *SliverConsoleClient) triggerReactions(event *clientpb.Event) {
|
||||
reactions := core.Reactions.On(event.EventType)
|
||||
if len(reactions) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// We need some special handling for SessionOpenedEvent to
|
||||
// set the new session as the active session
|
||||
currentActiveSession := con.ActiveSession.Get()
|
||||
defer con.ActiveSession.Set(currentActiveSession)
|
||||
con.ActiveSession.Set(nil)
|
||||
if event.EventType == consts.SessionOpenedEvent {
|
||||
if event.Session == nil || event.Session.OS == "" {
|
||||
return // Half-open session, do not execute any command
|
||||
}
|
||||
con.ActiveSession.Set(event.Session)
|
||||
}
|
||||
|
||||
for _, reaction := range reactions {
|
||||
for _, line := range reaction.Commands {
|
||||
con.PrintInfof("Execute reaction: '%s'\n", line)
|
||||
args, err := shlex.Split(line, true)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Reaction command has invalid args: %s\n", err)
|
||||
continue
|
||||
}
|
||||
err = con.App.RunCommand(args)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Reaction command error: %s\n", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (con *SliverConsoleClient) GetPrompt() string {
|
||||
prompt := Underline + "sliver" + Normal
|
||||
if con.IsServer {
|
||||
@@ -270,16 +233,16 @@ func (con *SliverConsoleClient) PrintLogo() {
|
||||
|
||||
insecureRand.Seed(time.Now().Unix())
|
||||
logo := asciiLogos[insecureRand.Intn(len(asciiLogos))]
|
||||
con.Println(logo)
|
||||
con.Println("All hackers gain " + abilities[insecureRand.Intn(len(abilities))])
|
||||
con.Printf(Info+"Server v%s - %s%s\n", serverSemVer, serverVer.Commit, dirty)
|
||||
fmt.Println(logo)
|
||||
fmt.Println("All hackers gain " + abilities[insecureRand.Intn(len(abilities))])
|
||||
fmt.Printf(Info+"Server v%s - %s%s\n", serverSemVer, serverVer.Commit, dirty)
|
||||
if version.GitCommit != serverVer.Commit {
|
||||
con.Printf(Info+"Client %s\n", version.FullVersion())
|
||||
fmt.Printf(Info+"Client %s\n", version.FullVersion())
|
||||
}
|
||||
con.Println(Info + "Welcome to the sliver shell, please type 'help' for options")
|
||||
con.Println()
|
||||
fmt.Println(Info + "Welcome to the sliver shell, please type 'help' for options")
|
||||
fmt.Println()
|
||||
if serverVer.Major != int32(version.SemanticVersion()[0]) {
|
||||
con.Printf(Warn + "Warning: Client and server may be running incompatible versions.\n")
|
||||
fmt.Printf(Warn + "Warning: Client and server may be running incompatible versions.\n")
|
||||
}
|
||||
con.CheckLastUpdate()
|
||||
}
|
||||
@@ -296,7 +259,7 @@ func (con *SliverConsoleClient) CheckLastUpdate() {
|
||||
day := 24 * time.Hour
|
||||
if compiledAt.Add(30 * day).Before(now) {
|
||||
if lastUpdate == nil || lastUpdate.Add(30*day).Before(now) {
|
||||
con.Printf(Info + "Check for updates with the 'update' command\n\n")
|
||||
fmt.Printf(Info + "Check for updates with the 'update' command\n\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -349,8 +312,7 @@ func (con *SliverConsoleClient) GetSessionsByName(name string) []*clientpb.Sessi
|
||||
return matched
|
||||
}
|
||||
|
||||
// GetActiveSessionConfig - Get the active sessions's config
|
||||
func (con *SliverConsoleClient) GetActiveSessionConfig() *clientpb.ImplantConfig {
|
||||
func (con *SliverConsoleClient) GetActiveSliverConfig() *clientpb.ImplantConfig {
|
||||
session := con.ActiveSession.Get()
|
||||
if session == nil {
|
||||
return nil
|
||||
|
||||
@@ -105,16 +105,11 @@ const (
|
||||
ProfilesStr = "profiles"
|
||||
|
||||
// Generic
|
||||
|
||||
// NewStr - "new"
|
||||
NewStr = "new"
|
||||
AddStr = "add"
|
||||
StartStr = "start"
|
||||
StopStr = "stop"
|
||||
SetStr = "set"
|
||||
UnsetStr = "unset"
|
||||
SaveStr = "save"
|
||||
ReloadStr = "reload"
|
||||
NewStr = "new"
|
||||
AddStr = "add"
|
||||
StartStr = "start"
|
||||
StopStr = "stop"
|
||||
// RmStr - Already defined below for "rm" command
|
||||
|
||||
LootStr = "loot"
|
||||
LootLocalStr = "local"
|
||||
@@ -186,7 +181,9 @@ const (
|
||||
PsExecStr = "psexec"
|
||||
BackdoorStr = "backdoor"
|
||||
MakeTokenStr = "make-token"
|
||||
EnvStr = "env"
|
||||
GetEnvStr = "getenv"
|
||||
SetEnvStr = "setenv"
|
||||
UnsetEnvStr = "unsetenv"
|
||||
RegistryStr = "registry"
|
||||
RegistryReadStr = "read"
|
||||
RegistryWriteStr = "write"
|
||||
@@ -202,8 +199,6 @@ const (
|
||||
|
||||
PortfwdStr = "portfwd"
|
||||
|
||||
ReactionStr = "reaction"
|
||||
|
||||
LicensesStr = "licenses"
|
||||
)
|
||||
|
||||
|
||||
@@ -1,118 +0,0 @@
|
||||
package core
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2021 Bishop Fox
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
consts "github.com/bishopfox/sliver/client/constants"
|
||||
)
|
||||
|
||||
var (
|
||||
// Reactions - Manages/tracks reactions
|
||||
Reactions = &reactions{
|
||||
reactionMap: map[string][]Reaction{},
|
||||
mutex: &sync.RWMutex{},
|
||||
}
|
||||
|
||||
// ReactableEvents - A list of reactionable events
|
||||
ReactableEvents = []string{
|
||||
consts.SessionOpenedEvent,
|
||||
consts.SessionUpdateEvent,
|
||||
consts.SessionClosedEvent,
|
||||
consts.CanaryEvent,
|
||||
consts.WatchtowerEvent,
|
||||
consts.LootAddedEvent,
|
||||
consts.LootRemovedEvent,
|
||||
|
||||
// Not sure if we want to add these or not:
|
||||
// consts.JobStartedEvent,
|
||||
// consts.JobStoppedEvent,
|
||||
// consts.BuildEvent,
|
||||
// consts.BuildCompletedEvent,
|
||||
// consts.ProfileEvent,
|
||||
// consts.WebsiteEvent,
|
||||
}
|
||||
)
|
||||
|
||||
type reactions struct {
|
||||
reactionMap map[string][]Reaction
|
||||
mutex *sync.RWMutex
|
||||
reactionID int
|
||||
}
|
||||
|
||||
// Add - Add a reaction
|
||||
func (r *reactions) Add(reaction Reaction) Reaction {
|
||||
r.mutex.Lock()
|
||||
defer r.mutex.Unlock()
|
||||
r.reactionID++
|
||||
reaction.ID = r.reactionID
|
||||
if eventActions, ok := r.reactionMap[reaction.EventType]; ok {
|
||||
r.reactionMap[reaction.EventType] = append(eventActions, reaction)
|
||||
} else {
|
||||
r.reactionMap[reaction.EventType] = []Reaction{reaction}
|
||||
}
|
||||
return reaction
|
||||
}
|
||||
|
||||
// Remove - Remove a reaction, yes we're using linear search but this isn't exactly
|
||||
// a performance critical piece of code and the map/slice is going to be very small
|
||||
func (r *reactions) Remove(reactionID int) bool {
|
||||
r.mutex.Lock()
|
||||
defer r.mutex.Unlock()
|
||||
for eventType, eventReactions := range r.reactionMap {
|
||||
for index, reaction := range eventReactions {
|
||||
if reaction.ID == reactionID {
|
||||
r.reactionMap[eventType] = append(eventReactions[:index], eventReactions[index+1:]...)
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// On - Get all reactions of a specific type
|
||||
func (r *reactions) On(eventType string) []Reaction {
|
||||
r.mutex.RLock()
|
||||
defer r.mutex.RUnlock()
|
||||
if reactions, ok := r.reactionMap[eventType]; ok {
|
||||
tmp := make([]Reaction, len(reactions))
|
||||
copy(tmp, reactions)
|
||||
return tmp
|
||||
}
|
||||
return []Reaction{}
|
||||
}
|
||||
|
||||
// All - Get all reactions (returns a flat list with all event types)
|
||||
func (r *reactions) All() []Reaction {
|
||||
r.mutex.RLock()
|
||||
defer r.mutex.RUnlock()
|
||||
reactions := []Reaction{}
|
||||
for _, eventReactions := range r.reactionMap {
|
||||
reactions = append(reactions, eventReactions...)
|
||||
}
|
||||
return reactions
|
||||
}
|
||||
|
||||
// Reaction - Metadata about a portfwd listener
|
||||
type Reaction struct {
|
||||
ID int `json:"-"`
|
||||
EventType string `json:"event_type"`
|
||||
Commands []string `json:"commands"`
|
||||
}
|
||||
Reference in new issue
Block a user