Compare commits

..
Author SHA1 Message Date
moloch-- 804e149113 Add shell kill option 2026-02-15 20:58:07 -08:00
moloch-- 9ced4cfd50 Add shell mgmt features 2026-02-15 20:51:18 -08:00
moloch-- b7abf98c95 Tweak yt header 2026-02-15 07:30:51 -08:00
moloch-- 918ac1bb91 Tweak payload compatibility tables 2026-02-14 16:24:44 -08:00
moloch-- 1e57818312 Tweak cross-compile docs 2026-02-14 11:34:11 -08:00
moloch-- c44c14dd46 Tweak cross-compile docs 2026-02-14 11:33:48 -08:00
moloch-- 35f41cd62d Tweak compile from source instructions 2026-02-14 11:29:35 -08:00
moloch-- 4eff1cc053 Add getting started workshop 2026-02-14 11:22:12 -08:00
moloch-- 16981da431 upgrade docs to nextjs v16 2026-02-14 11:16:38 -08:00
moloch-- 3830c76292 npm update 2026-02-14 09:43:13 -08:00
moloch-- 73de93d6fb Fix content scrolling 2026-02-14 09:42:04 -08:00
moloch-- c7770c2f6e Add more yt links 2026-02-14 09:31:02 -08:00
moloch-- 21db908ce4 Hide footer on yt play 2026-02-14 09:23:11 -08:00
moloch-- b7b0247652 Add basic yt embeds 2026-02-14 08:52:15 -08:00
moloch-- 3dc7dc03d0 Fix moving sidebar 2026-02-14 08:40:26 -08:00
moloch-- 229452d7ed Fix moving sidebar 2026-02-14 08:39:23 -08:00
moloch-- d29ce9a08b Add payload compatibility table to docs 2026-02-14 08:19:18 -08:00
Joe 46f2b2b402 Merge pull request #2177 from BishopFox/skoveit-feature/dynamic-c2-endpoint
Skoveit feature/dynamic c2 endpoint
2026-02-13 11:50:36 -07:00
Joe 6ee2006387 Merge pull request #2170 from xtle0o0/fix/external-builder-authz-scope
Restrict external builder RPCs to assigned build jobs
2026-02-13 11:50:22 -07:00
moloch-- 2fd13ee2bc Regenerate pb 2026-02-13 07:22:06 -08:00
0xOral 2697568336 fix-export-ca-error-message 2026-02-13 07:21:54 -08:00
Joe 5013048b0b Merge pull request #2176 from skoveit/fix-export-ca-error-message
fix-export-ca-error-message
2026-02-13 08:13:46 -07:00
Joe 627b15572c Merge branch 'master' into feature/dynamic-c2-endpoint 2026-02-13 08:11:56 -07:00
0xOral 42c40c9b8d fix-export-ca-error-message 2026-02-12 19:45:52 +02:00
0xOral 405b282fed Dynamic C2 Endpoint Switching 2026-02-12 18:47:26 +02:00
Joe e32cd36e01 Merge pull request #2172 from skoveit/fix-off-by-one-naming
Fix: server panic on single-word lists and off-by-one in codename generation
2026-02-10 22:30:32 -07:00
Joe 492a2aeed6 Merge pull request #2171 from loresuso/loresuso/fix-zombie-shell
fix: wait system shell to cleanup zombie processes
2026-02-10 22:29:53 -07:00
Lorenzo Susini d6dafb0391 fix: wait system shell to cleanup zombie processes
Signed-off-by: Lorenzo Susini <[email protected]>
2026-02-10 18:01:39 +01:00
0xOral 49a1bcfd7d fix(codenames): fix off-by-one error and prevent panic on single-word lists 2026-02-10 17:05:13 +02:00
xtle0o0 a8d1bf6fb1 Restrict external builder RPCs to assigned builds 2026-02-10 12:00:24 +01:00
moloch-- 2f386b7e09 v1.7.1 2026-02-08 14:19:47 -08:00
Joe d7dce58397 Merge pull request #2168 from BishopFox/feature/mutli-console
Feature/mutli console
2026-02-08 07:47:05 -07:00
moloch-- 15114138a8 Save interactive settings to disk 2026-02-08 06:32:17 -08:00
moloch-- 2716090410 Add custom prompt configurations 2026-02-08 06:28:45 -08:00
moloch-- a805161245 Add themes and console switcher 2026-02-07 15:46:17 -08:00
Joe afd57891cd Merge pull request #2166 from BishopFox/linux/shellcode
Linux/shellcode
2026-02-07 14:32:19 -07:00
moloch--andCodex bdf4a6ed0d Codex changes
Co-authored-by: Codex
2026-02-07 12:54:03 -08:00
108 changed files with 7154 additions and 2128 deletions

No files matched your search

+1
View File
@@ -42,3 +42,4 @@ See the [wiki](https://sliver.sh/docs?name=Compile+from+Source).
### 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.
+53
View File
@@ -22,6 +22,7 @@ import (
"encoding/json"
"os"
"path/filepath"
"strings"
"gopkg.in/yaml.v3"
)
@@ -31,6 +32,18 @@ const (
settingsLegacyFileName = "tui-settings.json"
)
const (
// Accepted values for tui-settings.yaml "prompt:":
// - host: show "[host]" prefix on sliver-client, "[server]" on server console
// - operator-host: show "[operator@host]" prefix on sliver-client, "[server]" on server console
// - basic: show "sliver >" (no prefix)
// - custom: render full prompt from prompt_template
PromptStyleHost = "host"
PromptStyleOperatorHost = "operator-host"
PromptStyleBasic = "basic"
PromptStyleCustom = "custom"
)
// ClientSettings - Client JSON config
type ClientSettings struct {
TableStyle string `json:"tables" yaml:"tables"`
@@ -41,6 +54,8 @@ type ClientSettings struct {
VimMode bool `json:"vim_mode" yaml:"vim_mode"`
UserConnect bool `json:"user_connect" yaml:"user_connect"`
ConsoleLogs bool `json:"console_logs" yaml:"console_logs"`
PromptStyle string `json:"prompt" yaml:"prompt"`
PromptTemplate string `json:"prompt_template" yaml:"prompt_template"`
}
// LoadSettings - Load the client settings from disk
@@ -67,6 +82,8 @@ func LoadSettings() (*ClientSettings, error) {
return defaultSettings(), err
}
// Ensure any missing/unknown values are coerced to a supported prompt style.
settings.PromptStyle = NormalizePromptStyle(settings.PromptStyle)
if err := SaveSettings(settings); err != nil {
return settings, err
}
@@ -78,6 +95,38 @@ func LoadSettings() (*ClientSettings, error) {
return settings, nil
}
// NormalizePromptStyle canonicalizes prompt style strings and returns a safe
// default if the value is empty/unknown.
func NormalizePromptStyle(v string) string {
switch strings.ToLower(strings.TrimSpace(v)) {
case PromptStyleOperatorHost:
return PromptStyleOperatorHost
case PromptStyleHost:
return PromptStyleHost
case PromptStyleBasic:
return PromptStyleBasic
case PromptStyleCustom:
return PromptStyleCustom
// Backward compatible aliases.
case "show host":
return PromptStyleHost
case "show user and host":
return PromptStyleOperatorHost
case "show operator and host":
return PromptStyleOperatorHost
case "operator@host":
return PromptStyleOperatorHost
case "user@host":
return PromptStyleOperatorHost
case "":
return PromptStyleHost
default:
return PromptStyleHost
}
}
func defaultSettings() *ClientSettings {
return &ClientSettings{
TableStyle: "SliverDefault",
@@ -87,9 +136,13 @@ func defaultSettings() *ClientSettings {
AlwaysOverflow: false,
VimMode: false,
ConsoleLogs: true,
PromptStyle: PromptStyleHost,
PromptTemplate: DefaultPromptTemplate,
}
}
const DefaultPromptTemplate = `{{- if .IsServer -}}{{ .Styles.Bold.Render "[server]" }} {{ .Styles.Underline.Render "sliver" }}{{ .Target.Suffix }} > {{- else -}}{{- if .Host -}}{{ .Styles.BoldPrimary.Render (printf "[%s]" .Host) }} {{- end -}}{{ .Styles.Underline.Render "sliver" }}{{ .Target.Suffix }} > {{- end -}}`
// SaveSettings - Save the current settings to disk
func SaveSettings(settings *ClientSettings) error {
rootDir, _ := filepath.Abs(GetRootAppDir())
+22
View File
@@ -21,6 +21,8 @@ always_overflow: true
vim_mode: true
user_connect: true
console_logs: false
prompt: "basic"
prompt_template: "{{.Host}}"
`)
if err := os.WriteFile(settingsPath, data, 0o600); err != nil {
t.Fatalf("failed to write yaml settings: %v", err)
@@ -54,6 +56,12 @@ console_logs: false
if settings.ConsoleLogs {
t.Fatalf("expected ConsoleLogs false")
}
if settings.PromptStyle != PromptStyleBasic {
t.Fatalf("expected PromptStyle %q, got %q", PromptStyleBasic, settings.PromptStyle)
}
if settings.PromptTemplate != "{{.Host}}" {
t.Fatalf("expected PromptTemplate %q, got %q", "{{.Host}}", settings.PromptTemplate)
}
}
func TestLoadSettingsMigratesLegacyJSON(t *testing.T) {
@@ -71,6 +79,8 @@ func TestLoadSettingsMigratesLegacyJSON(t *testing.T) {
VimMode: true,
UserConnect: true,
ConsoleLogs: false,
PromptStyle: PromptStyleOperatorHost,
PromptTemplate: "{{.Operator}}@{{.Host}} sliver > ",
}
data, err := json.MarshalIndent(legacy, "", " ")
if err != nil {
@@ -87,6 +97,12 @@ func TestLoadSettingsMigratesLegacyJSON(t *testing.T) {
if settings.TableStyle != "Legacy" {
t.Fatalf("expected TableStyle %q, got %q", "Legacy", settings.TableStyle)
}
if settings.PromptStyle != PromptStyleOperatorHost {
t.Fatalf("expected PromptStyle %q, got %q", PromptStyleOperatorHost, settings.PromptStyle)
}
if settings.PromptTemplate != "{{.Operator}}@{{.Host}} sliver > " {
t.Fatalf("expected PromptTemplate %q, got %q", "{{.Operator}}@{{.Host}} sliver > ", settings.PromptTemplate)
}
if _, err := os.Stat(filepath.Join(rootDir, settingsFileName)); err != nil {
t.Fatalf("expected yaml settings to exist: %v", err)
}
@@ -108,6 +124,12 @@ func TestLoadSettingsWritesDefault(t *testing.T) {
if settings.TableStyle != "SliverDefault" {
t.Fatalf("expected default TableStyle %q, got %q", "SliverDefault", settings.TableStyle)
}
if settings.PromptStyle != PromptStyleHost {
t.Fatalf("expected default PromptStyle %q, got %q", PromptStyleHost, settings.PromptStyle)
}
if settings.PromptTemplate != DefaultPromptTemplate {
t.Fatalf("expected default PromptTemplate %q, got %q", DefaultPromptTemplate, settings.PromptTemplate)
}
rootDir, _ := filepath.Abs(GetRootAppDir())
if _, err := os.Stat(filepath.Join(rootDir, settingsFileName)); err != nil {
t.Fatalf("expected default settings to be written: %v", err)
+6 -6
View File
@@ -26,16 +26,16 @@ import (
"github.com/bishopfox/sliver/client/forms"
)
func selectConfig() *assets.ClientConfig {
func selectConfig() (string, *assets.ClientConfig) {
configs := assets.GetConfigs()
if len(configs) == 0 {
return nil
return "", nil
}
if len(configs) == 1 {
for _, config := range configs {
return config
for key, config := range configs {
return key, config
}
}
@@ -49,8 +49,8 @@ func selectConfig() *assets.ClientConfig {
err := forms.Select("Select a server:", keys, &selection)
if err != nil {
fmt.Println(err.Error())
return nil
return "", nil
}
return configs[selection]
return selection, configs[selection]
}
+26
View File
@@ -0,0 +1,26 @@
package cli
import (
"context"
"fmt"
"os"
"google.golang.org/grpc"
"google.golang.org/grpc/connectivity"
)
func handleConnectionLost(ln *grpc.ClientConn) {
if ln == nil {
return
}
currentState := ln.GetState()
// currentState should be "Ready" when the connection is established.
if ln.WaitForStateChange(context.Background(), currentState) {
newState := ln.GetState()
// newState will be "Idle" if the connection is lost.
if newState == connectivity.Idle {
fmt.Println("\nLost connection to server. Exiting now.")
os.Exit(1)
}
}
}
+4 -29
View File
@@ -19,9 +19,7 @@ package cli
*/
import (
"context"
"fmt"
"os"
"github.com/bishopfox/sliver/client/assets"
"github.com/bishopfox/sliver/client/command"
@@ -30,7 +28,6 @@ import (
"github.com/bishopfox/sliver/protobuf/rpcpb"
"github.com/spf13/cobra"
"google.golang.org/grpc"
"google.golang.org/grpc/connectivity"
)
// consoleCmd generates the console with required pre/post runners.
@@ -46,8 +43,6 @@ func consoleCmd(con *console.SliverClient) *cobra.Command {
}
func consoleRunnerCmd(con *console.SliverClient, run bool) (pre, post func(cmd *cobra.Command, args []string) error) {
var ln *grpc.ClientConn
pre = func(cmd *cobra.Command, _ []string) error {
configs := assets.GetConfigs()
@@ -55,7 +50,7 @@ func consoleRunnerCmd(con *console.SliverClient, run bool) (pre, post func(cmd *
fmt.Printf("No config files found at %s (see --help)\n", assets.GetConfigDir())
return nil
}
config := selectConfig()
configKey, config := selectConfig()
if config == nil {
return nil
}
@@ -72,40 +67,20 @@ func consoleRunnerCmd(con *console.SliverClient, run bool) (pre, post func(cmd *
}
var rpc rpcpb.SliverRPCClient
var ln *grpc.ClientConn
rpc, ln, err = transport.MTLSConnect(config)
if err != nil {
fmt.Printf("Connection to server failed %s", err)
return nil
}
// Wait for any connection state changes and exit if the connection is lost.
go handleConnectionLost(ln)
return console.StartClient(con, rpc, command.ServerCommands(con, nil), command.SliverCommands(con), run, rcScript)
return console.StartClient(con, rpc, ln, &console.ConnectionDetails{ConfigKey: configKey, Config: config}, command.ServerCommands(con, nil), command.SliverCommands(con), run, rcScript)
}
// Close the RPC connection once exiting
post = func(_ *cobra.Command, _ []string) error {
if ln != nil {
return ln.Close()
}
return nil
return con.CloseConnection()
}
return pre, post
}
func handleConnectionLost(ln *grpc.ClientConn) {
currentState := ln.GetState()
// currentState should be "Ready" when the connection is established.
if ln.WaitForStateChange(context.Background(), currentState) {
newState := ln.GetState()
// newState will be "Idle" if the connection is lost.
if newState == connectivity.Idle {
fmt.Println("\nLost connection to server. Exiting now.")
os.Exit(1)
}
}
}
+15 -15
View File
@@ -428,7 +428,7 @@ func PrintArmoryPackages(aliases []*alias.AliasManifest, exts []*extensions.Exte
tw := table.NewWriter()
tw.SetStyle(settings.GetTableStyle(con))
tw.SetTitle(console.Bold + "Packages" + console.Normal)
tw.SetTitle(console.StyleBold.Render("Packages"))
urlMargin := 150 // Extra margin needed to show URL column
@@ -493,26 +493,26 @@ func PrintArmoryPackages(aliases []*alias.AliasManifest, exts []*extensions.Exte
rows := []table.Row{}
for _, pkg := range entries {
color := console.Normal
style := console.StyleNormal
if extensions.CmdExists(pkg.CommandName, sliverMenu.Command) {
color = console.Green
style = console.StyleGreen
}
if con.Settings.SmallTermWidth+urlMargin < width {
rows = append(rows, table.Row{
fmt.Sprintf(color+"%s"+console.Normal, pkg.ArmoryName),
fmt.Sprintf(color+"%s"+console.Normal, pkg.CommandName),
fmt.Sprintf(color+"%s"+console.Normal, pkg.Version),
fmt.Sprintf(color+"%s"+console.Normal, pkg.Type),
fmt.Sprintf(color+"%s"+console.Normal, pkg.Help),
fmt.Sprintf(color+"%s"+console.Normal, pkg.URL),
style.Render(pkg.ArmoryName),
style.Render(pkg.CommandName),
style.Render(pkg.Version),
style.Render(pkg.Type),
style.Render(pkg.Help),
style.Render(pkg.URL),
})
} else {
rows = append(rows, table.Row{
fmt.Sprintf(color+"%s"+console.Normal, pkg.ArmoryName),
fmt.Sprintf(color+"%s"+console.Normal, pkg.CommandName),
fmt.Sprintf(color+"%s"+console.Normal, pkg.Version),
fmt.Sprintf(color+"%s"+console.Normal, pkg.Type),
fmt.Sprintf(color+"%s"+console.Normal, pkg.Help),
style.Render(pkg.ArmoryName),
style.Render(pkg.CommandName),
style.Render(pkg.Version),
style.Render(pkg.Type),
style.Render(pkg.Help),
})
}
}
@@ -524,7 +524,7 @@ func PrintArmoryPackages(aliases []*alias.AliasManifest, exts []*extensions.Exte
func PrintArmoryBundles(bundles []*ArmoryBundle, con *console.SliverClient) {
tw := table.NewWriter()
tw.SetStyle(settings.GetTableStyle(con))
tw.SetTitle(console.Bold + "Bundles" + console.Normal)
tw.SetTitle(console.StyleBold.Render("Bundles"))
tw.AppendHeader(table.Row{
"Armory Name",
"Name",
+1 -1
View File
@@ -257,7 +257,7 @@ func displayAvailableUpdates(con *console.SliverClient, updateKeys []UpdateIdent
if len(extensionUpdates) != 1 {
extensionSuffix = "s"
}
tw.SetTitle(console.Bold + fmt.Sprintf(title, len(aliasUpdates), aliasSuffix, len(extensionUpdates), extensionSuffix) + console.Normal)
tw.SetTitle(console.StyleBold.Render(fmt.Sprintf(title, len(aliasUpdates), aliasSuffix, len(extensionUpdates), extensionSuffix)))
tw.AppendHeader(table.Row{
"Package Name",
"Package Type",
+19 -19
View File
@@ -178,10 +178,10 @@ func renderBeacons(beacons []*clientpb.Beacon, filter string, filterRegex *regex
}
for _, beacon := range beacons {
color := console.Normal
style := console.StyleNormal
activeBeacon := con.ActiveTarget.GetBeacon()
if activeBeacon != nil && activeBeacon.ID == beacon.ID {
color = console.Green
style = console.StyleGreen
}
if beacon.Integrity == "" {
beacon.Integrity = "-"
@@ -192,34 +192,34 @@ func renderBeacons(beacons []*clientpb.Beacon, filter string, filterRegex *regex
if wideTermWidth {
rowEntries = []string{
fmt.Sprintf(color+"%s"+console.Normal, strings.Split(beacon.ID, "-")[0]),
fmt.Sprintf(color+"%s"+console.Normal, beacon.Name),
fmt.Sprintf(color+"%d/%d"+console.Normal, beacon.TasksCountCompleted, beacon.TasksCount),
fmt.Sprintf(color+"%s"+console.Normal, beacon.Transport),
fmt.Sprintf(color+"%s"+console.Normal, beacon.RemoteAddress),
fmt.Sprintf(color+"%s"+console.Normal, beacon.Hostname),
fmt.Sprintf(color+"%s"+console.Normal, strings.TrimPrefix(beacon.Username, beacon.Hostname+"\\")),
fmt.Sprintf(color+"%s (%d)"+console.Normal, beacon.Filename, beacon.PID),
style.Render(strings.Split(beacon.ID, "-")[0]),
style.Render(beacon.Name),
style.Render(fmt.Sprintf("%d/%d", beacon.TasksCountCompleted, beacon.TasksCount)),
style.Render(beacon.Transport),
style.Render(beacon.RemoteAddress),
style.Render(beacon.Hostname),
style.Render(strings.TrimPrefix(beacon.Username, beacon.Hostname+"\\")),
style.Render(fmt.Sprintf("%s (%d)", beacon.Filename, beacon.PID)),
}
if windowsBeaconInList {
rowEntries = append(rowEntries, fmt.Sprintf(color+"%s"+console.Normal, beacon.Integrity))
rowEntries = append(rowEntries, style.Render(beacon.Integrity))
}
rowEntries = append(rowEntries, []string{
fmt.Sprintf(color+"%s/%s"+console.Normal, beacon.OS, beacon.Arch),
fmt.Sprintf(color+"%s"+console.Normal, beacon.Locale),
style.Render(fmt.Sprintf("%s/%s", beacon.OS, beacon.Arch)),
style.Render(beacon.Locale),
con.FormatDateDelta(time.Unix(beacon.LastCheckin, 0), wideTermWidth, false),
con.FormatDateDelta(time.Unix(beacon.NextCheckin, 0), wideTermWidth, true),
}...)
} else {
rowEntries = []string{
fmt.Sprintf(color+"%s"+console.Normal, strings.Split(beacon.ID, "-")[0]),
fmt.Sprintf(color+"%s"+console.Normal, beacon.Name),
fmt.Sprintf(color+"%s"+console.Normal, beacon.Transport),
fmt.Sprintf(color+"%s"+console.Normal, beacon.Hostname),
fmt.Sprintf(color+"%s"+console.Normal, strings.TrimPrefix(beacon.Username, beacon.Hostname+"\\")),
fmt.Sprintf(color+"%s/%s"+console.Normal, beacon.OS, beacon.Arch),
style.Render(strings.Split(beacon.ID, "-")[0]),
style.Render(beacon.Name),
style.Render(beacon.Transport),
style.Render(beacon.Hostname),
style.Render(strings.TrimPrefix(beacon.Username, beacon.Hostname+"\\")),
style.Render(fmt.Sprintf("%s/%s", beacon.OS, beacon.Arch)),
con.FormatDateDelta(time.Unix(beacon.LastCheckin, 0), wideTermWidth, false),
con.FormatDateDelta(time.Unix(beacon.NextCheckin, 0), wideTermWidth, true),
}
+12 -13
View File
@@ -2,7 +2,6 @@ package certificates
import (
"context"
"fmt"
"time"
"github.com/bishopfox/sliver/client/command/settings"
@@ -58,28 +57,28 @@ func printCertificateAuthorityInfo(con *console.SliverClient, authData []*client
}
for _, authority := range authData {
rowColor := console.Normal
rowStyle := console.StyleNormal
expiry, err := time.Parse(timeFormat, authority.ValidityExpiry)
if err == nil {
rowColor = checkCertExpiry(expiry)
rowStyle = checkCertExpiry(expiry)
}
if wideTermWidth {
tw.AppendRow(table.Row{
fmt.Sprintf(rowColor+"%s"+console.Normal, authority.ID),
fmt.Sprintf(rowColor+"%s"+console.Normal, authority.CN),
fmt.Sprintf(rowColor+"%s"+console.Normal, authority.CreationTime),
fmt.Sprintf(rowColor+"%s"+console.Normal, authority.Type),
fmt.Sprintf(rowColor+"%s"+console.Normal, authority.KeyAlgorithm),
fmt.Sprintf(rowColor+"%s"+console.Normal, authority.ValidityStart),
fmt.Sprintf(rowColor+"%s"+console.Normal, authority.ValidityExpiry),
rowStyle.Render(authority.ID),
rowStyle.Render(authority.CN),
rowStyle.Render(authority.CreationTime),
rowStyle.Render(authority.Type),
rowStyle.Render(authority.KeyAlgorithm),
rowStyle.Render(authority.ValidityStart),
rowStyle.Render(authority.ValidityExpiry),
})
} else {
tw.AppendRow(table.Row{
fmt.Sprintf(rowColor+"%s"+console.Normal, authority.ID),
fmt.Sprintf(rowColor+"%s"+console.Normal, authority.CN),
fmt.Sprintf(rowColor+"%s"+console.Normal, authority.ValidityExpiry),
rowStyle.Render(authority.ID),
rowStyle.Render(authority.CN),
rowStyle.Render(authority.ValidityExpiry),
})
}
}
+17 -18
View File
@@ -2,7 +2,6 @@ package certificates
import (
"context"
"fmt"
"time"
"github.com/bishopfox/sliver/client/command/settings"
@@ -95,22 +94,22 @@ func CertificateInfoCmd(cmd *cobra.Command, con *console.SliverClient, args []st
printCertificateInfo(con, certificateInfo.Info)
}
func checkCertExpiry(expiryTime time.Time) string {
func checkCertExpiry(expiryTime time.Time) console.TextStyle {
if expiryTime.Before(time.Now()) || expiryTime.Equal(time.Now()) {
return console.Bold + console.Red
return console.StyleBoldRed
}
// One week is 168 hours - this is bad
if expiryTime.Before(time.Now().Add(168 * time.Hour)) {
return console.Bold + console.Red
return console.StyleBoldRed
}
// One month is approximately 730 hours - this is a warning
if expiryTime.Before(time.Now().Add(730 * time.Hour)) {
return console.Bold + console.Orange
return console.StyleBoldOrange
}
return console.Normal
return console.StyleNormal
}
func printCertificateInfo(con *console.SliverClient, certData []*clientpb.CertificateData) {
@@ -148,28 +147,28 @@ func printCertificateInfo(con *console.SliverClient, certData []*clientpb.Certif
}
for _, cert := range certData {
rowColor := console.Normal
rowStyle := console.StyleNormal
expiry, err := time.Parse(timeFormat, cert.ValidityExpiry)
// This should not error out, but if it does, the row will not be colored
if err == nil {
rowColor = checkCertExpiry(expiry)
rowStyle = checkCertExpiry(expiry)
}
if wideTermWidth {
tw.AppendRow(table.Row{
fmt.Sprintf(rowColor+"%s"+console.Normal, cert.ID),
fmt.Sprintf(rowColor+"%s"+console.Normal, cert.CN),
fmt.Sprintf(rowColor+"%s"+console.Normal, cert.CreationTime),
fmt.Sprintf(rowColor+"%s"+console.Normal, cert.Type),
fmt.Sprintf(rowColor+"%s"+console.Normal, cert.KeyAlgorithm),
fmt.Sprintf(rowColor+"%s"+console.Normal, cert.ValidityStart),
fmt.Sprintf(rowColor+"%s"+console.Normal, cert.ValidityExpiry),
rowStyle.Render(cert.ID),
rowStyle.Render(cert.CN),
rowStyle.Render(cert.CreationTime),
rowStyle.Render(cert.Type),
rowStyle.Render(cert.KeyAlgorithm),
rowStyle.Render(cert.ValidityStart),
rowStyle.Render(cert.ValidityExpiry),
})
} else {
tw.AppendRow(table.Row{
fmt.Sprintf(rowColor+"%s"+console.Normal, cert.ID),
fmt.Sprintf(rowColor+"%s"+console.Normal, cert.CN),
fmt.Sprintf(rowColor+"%s"+console.Normal, cert.ValidityExpiry),
rowStyle.Render(cert.ID),
rowStyle.Render(cert.CN),
rowStyle.Render(cert.ValidityExpiry),
})
}
+3 -3
View File
@@ -106,17 +106,17 @@ func PrintCrackFiles(crackFiles *clientpb.CrackFiles, con *console.SliverClient)
func PrintCrackFilesByType(crackFiles *clientpb.CrackFiles, con *console.SliverClient) {
wordlistTable := table.NewWriter()
wordlistTable.SetTitle(console.Bold + "Wordlists" + console.Normal)
wordlistTable.SetTitle(console.StyleBold.Render("Wordlists"))
wordlistTable.SetStyle(settings.GetTableStyle(con))
wordlistTable.AppendHeader(table.Row{"Name", "Size"})
rulesTable := table.NewWriter()
rulesTable.SetTitle(console.Bold + "Rules" + console.Normal)
rulesTable.SetTitle(console.StyleBold.Render("Rules"))
rulesTable.SetStyle(settings.GetTableStyle(con))
rulesTable.AppendHeader(table.Row{"Name", "Size"})
hcTable := table.NewWriter()
hcTable.SetTitle(console.Bold + "Markov Hcstat2" + console.Normal)
hcTable.SetTitle(console.StyleBold.Render("Markov Hcstat2"))
hcTable.SetStyle(settings.GetTableStyle(con))
hcTable.AppendHeader(table.Row{"Name", "Size"})
+16 -16
View File
@@ -132,35 +132,35 @@ func PrintCrackers(crackers []*clientpb.Crackstation, con *console.SliverClient,
func printCracker(cracker *clientpb.Crackstation, index int, con *console.SliverClient, showBenchmarks bool) {
tw := table.NewWriter()
tw.SetStyle(settings.GetTableStyle(con))
tw.SetTitle(console.Bold + console.Orange + fmt.Sprintf(">>> Crackstation %02d - %s (%s)", index+1, cracker.Name, cracker.OperatorName) + console.Normal + "\n")
tw.SetTitle(console.StyleBoldOrange.Render(fmt.Sprintf(">>> Crackstation %02d - %s (%s)", index+1, cracker.Name, cracker.OperatorName)) + "\n")
tw.AppendSeparator()
tw.AppendRow(table.Row{console.Bold + "Operating System" + console.Normal, fmt.Sprintf("%s/%s", cracker.GOOS, cracker.GOARCH)})
tw.AppendRow(table.Row{console.Bold + "Hashcat Version" + console.Normal, cracker.HashcatVersion})
tw.AppendRow(table.Row{console.StyleBold.Render("Operating System"), fmt.Sprintf("%s/%s", cracker.GOOS, cracker.GOARCH)})
tw.AppendRow(table.Row{console.StyleBold.Render("Hashcat Version"), cracker.HashcatVersion})
if 0 < len(cracker.CUDA) {
for _, cuda := range cracker.CUDA {
tw.AppendSeparator()
tw.AppendRow(table.Row{console.Bold + "CUDA Device" + console.Normal, fmt.Sprintf(console.Bold+console.Green+"%s (%s)"+console.Normal, cuda.Name, cuda.Version)})
tw.AppendRow(table.Row{console.Bold + "Memory" + console.Normal, fmt.Sprintf("%s free of %s", cuda.MemoryFree, cuda.MemoryTotal)})
tw.AppendRow(table.Row{console.Bold + "Clock" + console.Normal, fmt.Sprintf("%d", cuda.Clock)})
tw.AppendRow(table.Row{console.Bold + "Processors" + console.Normal, fmt.Sprintf("%d", cuda.Processors)})
tw.AppendRow(table.Row{console.StyleBold.Render("CUDA Device"), console.StyleBoldGreen.Render(fmt.Sprintf("%s (%s)", cuda.Name, cuda.Version))})
tw.AppendRow(table.Row{console.StyleBold.Render("Memory"), fmt.Sprintf("%s free of %s", cuda.MemoryFree, cuda.MemoryTotal)})
tw.AppendRow(table.Row{console.StyleBold.Render("Clock"), fmt.Sprintf("%d", cuda.Clock)})
tw.AppendRow(table.Row{console.StyleBold.Render("Processors"), fmt.Sprintf("%d", cuda.Processors)})
}
}
if 0 < len(cracker.Metal) {
for _, metal := range cracker.Metal {
tw.AppendSeparator()
tw.AppendRow(table.Row{console.Bold + "Metal Device" + console.Normal, fmt.Sprintf(console.Bold+console.Green+"%s (%s)"+console.Normal, metal.Name, metal.Version)})
tw.AppendRow(table.Row{console.Bold + "Memory" + console.Normal, fmt.Sprintf("%s free of %s", metal.MemoryFree, metal.MemoryTotal)})
tw.AppendRow(table.Row{console.Bold + "Clock" + console.Normal, fmt.Sprintf("%d", metal.Clock)})
tw.AppendRow(table.Row{console.Bold + "Processors" + console.Normal, fmt.Sprintf("%d", metal.Processors)})
tw.AppendRow(table.Row{console.StyleBold.Render("Metal Device"), console.StyleBoldGreen.Render(fmt.Sprintf("%s (%s)", metal.Name, metal.Version))})
tw.AppendRow(table.Row{console.StyleBold.Render("Memory"), fmt.Sprintf("%s free of %s", metal.MemoryFree, metal.MemoryTotal)})
tw.AppendRow(table.Row{console.StyleBold.Render("Clock"), fmt.Sprintf("%d", metal.Clock)})
tw.AppendRow(table.Row{console.StyleBold.Render("Processors"), fmt.Sprintf("%d", metal.Processors)})
}
}
if 0 < len(cracker.OpenCL) {
for _, openCL := range cracker.OpenCL {
tw.AppendSeparator()
tw.AppendRow(table.Row{console.Bold + "OpenCL Device" + console.Normal, fmt.Sprintf(console.Bold+console.Green+console.Bold+"%s (%s)"+console.Normal, openCL.Name, openCL.Version)})
tw.AppendRow(table.Row{console.Bold + "Memory" + console.Normal, fmt.Sprintf("%s free of %s", openCL.MemoryFree, openCL.MemoryTotal)})
tw.AppendRow(table.Row{console.Bold + "Clock" + console.Normal, fmt.Sprintf("%d", openCL.Clock)})
tw.AppendRow(table.Row{console.Bold + "Processors" + console.Normal, fmt.Sprintf("%d", openCL.Processors)})
tw.AppendRow(table.Row{console.StyleBold.Render("OpenCL Device"), console.StyleBoldGreen.Render(fmt.Sprintf("%s (%s)", openCL.Name, openCL.Version))})
tw.AppendRow(table.Row{console.StyleBold.Render("Memory"), fmt.Sprintf("%s free of %s", openCL.MemoryFree, openCL.MemoryTotal)})
tw.AppendRow(table.Row{console.StyleBold.Render("Clock"), fmt.Sprintf("%d", openCL.Clock)})
tw.AppendRow(table.Row{console.StyleBold.Render("Processors"), fmt.Sprintf("%d", openCL.Processors)})
}
}
con.Printf("%s\n", tw.Render())
@@ -173,7 +173,7 @@ func printCracker(cracker *clientpb.Crackstation, index int, con *console.Sliver
func printBenchmarks(cracker *clientpb.Crackstation, con *console.SliverClient) {
tw := table.NewWriter()
tw.SetStyle(settings.GetTableStyle(con))
tw.SetTitle(console.Bold + "Benchmarks" + console.Normal)
tw.SetTitle(console.StyleBold.Render("Benchmarks"))
tw.SortBy([]table.SortBy{{Name: "Hash Type"}})
tw.AppendHeader(table.Row{"Hash Type", "Rate"})
if len(cracker.Benchmarks) == 0 {
+1 -1
View File
@@ -25,7 +25,7 @@ func CrackHcstat2Completer(con *console.SliverClient) carapace.Action {
}
results = append(results, file.Name)
desc := fmt.Sprintf("%s (%s chunks, chunk size: %s)", byteCountSI(file.UncompressedSize), len(file.Chunks), byteCountSI(file.ChunkSize))
desc := fmt.Sprintf("%s (%d chunks, chunk size: %s)", byteCountSI(file.UncompressedSize), len(file.Chunks), byteCountSI(file.ChunkSize))
results = append(results, desc)
}
+2 -2
View File
@@ -61,9 +61,9 @@ func printCollection(collection string, creds []*clientpb.Credential, con *conso
tw := table.NewWriter()
tw.SetStyle(settings.GetTableStyle(con))
if collection != "" {
tw.SetTitle(console.Bold + collection + console.Normal)
tw.SetTitle(console.StyleBold.Render(collection))
} else {
tw.SetTitle(console.Bold + "Default Collection" + console.Normal)
tw.SetTitle(console.StyleBold.Render("Default Collection"))
}
tw.AppendHeader(table.Row{
"ID",
+2 -2
View File
@@ -94,7 +94,7 @@ func CursedChromeCmd(cmd *cobra.Command, con *console.SliverClient, args []strin
}
if chromeExt != nil {
con.Printf("success!\n")
con.PrintInfof("Found viable Chrome extension %s%s%s (%s)\n", console.Bold, chromeExt.Title, console.Normal, chromeExt.ID)
con.PrintInfof("Found viable Chrome extension %s (%s)\n", console.StyleBold.Render(chromeExt.Title), chromeExt.ID)
con.PrintInfof("Injecting payload ... ")
cmd, _, _ := overlord.GetChromeContext(chromeExt.WebSocketDebuggerURL, curse)
// extCtxTimeout, cancel := context.WithTimeout(cmd, 10*time.Second)
@@ -120,7 +120,7 @@ func avadaKedavraChrome(session *clientpb.Session, cmd *cobra.Command, con *cons
if chromeProcess != nil {
con.PrintWarnf("Found running Chrome process: %d (ppid: %d)\n", chromeProcess.GetPid(), chromeProcess.GetPpid())
con.PrintWarnf("Sliver will need to kill and restart the Chrome process in order to perform code injection.\n")
con.PrintWarnf("Sliver will attempt to restore the user's session, however %sDATA LOSS MAY OCCUR!%s\n", console.Bold, console.Normal)
con.PrintWarnf("Sliver will attempt to restore the user's session, however %s\n", console.StyleBold.Render("DATA LOSS MAY OCCUR!"))
con.Printf("\n")
confirm := false
err = forms.Confirm("Kill and restore existing Chrome process?", &confirm)
+2 -2
View File
@@ -93,7 +93,7 @@ func startCursedConsole(curse *core.CursedProcess, helpers bool, target *overlor
tmpFile, _ := os.CreateTemp("", "cursed")
shell := readline.NewShell()
shell.History.AddFromFile("cursed history", tmpFile.Name())
shell.Prompt.Primary(func() string { return "\033[31mcursed »\033[0m " })
shell.Prompt.Primary(func() string { return console.StyleRed.Render("cursed »") + " " })
// EOFPrompt: "exit",
if con.Settings.VimMode {
@@ -111,7 +111,7 @@ func startCursedConsole(curse *core.CursedProcess, helpers bool, target *overlor
}
}
con.Printf(console.Bold+">>> Cursed Console, use ':help' for options%s\n\n", console.Normal)
con.Printf("%s\n\n", console.StyleBold.Render(">>> Cursed Console, use ':help' for options"))
for {
line, err := shell.Readline()
+2 -2
View File
@@ -77,7 +77,7 @@ func CursedEdgeCmd(cmd *cobra.Command, con *console.SliverClient, args []string)
}
if chromeExt != nil {
con.Printf("success!\n")
con.PrintInfof("Found viable Edge extension %s%s%s (%s)\n", console.Bold, chromeExt.Title, console.Normal, chromeExt.ID)
con.PrintInfof("Found viable Edge extension %s (%s)\n", console.StyleBold.Render(chromeExt.Title), chromeExt.ID)
con.PrintInfof("Injecting payload ... ")
cmd, _, _ := overlord.GetChromeContext(chromeExt.WebSocketDebuggerURL, curse)
// extCtxTimeout, cancel := context.WithTimeout(cmd, 10*time.Second)
@@ -103,7 +103,7 @@ func avadaKedavraEdge(session *clientpb.Session, cmd *cobra.Command, con *consol
if edgeProcess != nil {
con.PrintWarnf("Found running Edge process: %d (ppid: %d)\n", edgeProcess.GetPid(), edgeProcess.GetPpid())
con.PrintWarnf("Sliver will need to kill and restart the Edge process in order to perform code injection.\n")
con.PrintWarnf("Sliver will attempt to restore the user's session, however %sDATA LOSS MAY OCCUR!%s\n", console.Bold, console.Normal)
con.PrintWarnf("Sliver will attempt to restore the user's session, however %s\n", console.StyleBold.Render("DATA LOSS MAY OCCUR!"))
con.Printf("\n")
confirm := false
err = forms.Confirm("Kill and restore existing Edge process?", &confirm)
+1 -1
View File
@@ -85,7 +85,7 @@ func avadaKedavraElectron(electronExe string, session *clientpb.Session, cmd *co
if electronProcess != nil {
con.PrintWarnf("Found running '%s' process: %d (ppid: %d)\n", path.Base(electronExe), electronProcess.GetPid(), electronProcess.GetPpid())
con.PrintWarnf("Sliver will need to kill and restart the process in order to perform code injection.\n")
con.PrintWarnf("%sDATA LOSS MAY OCCUR!%s\n", console.Bold, console.Normal)
con.PrintWarnf("%s\n", console.StyleBold.Render("DATA LOSS MAY OCCUR!"))
con.Printf("\n")
confirm := false
err = forms.Confirm("Kill and restart the process?", &confirm)
+4 -5
View File
@@ -29,7 +29,6 @@ import (
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/sliverpb"
"github.com/fatih/color"
"github.com/spf13/cobra"
"google.golang.org/protobuf/proto"
)
@@ -239,19 +238,19 @@ func printGrep(grep *sliverpb.Grep, searchPattern string, searchPath string, cmd
// grepLineResult - Add color or formatting for results for console output
func grepLineResult(positions []*sliverpb.GrepLinePosition, line string, colorize bool, allowFormatting bool) string {
var result string = ""
var matchOutput func(a ...interface{}) string
var previousPositionEnd int32 = 0
matchStyle := console.StyleNormal
if colorize {
matchOutput = color.New(color.FgRed, color.Bold).SprintFunc()
matchStyle = console.StyleBoldRed
} else if allowFormatting {
matchOutput = color.New(color.Bold).SprintFunc()
matchStyle = console.StyleBold
}
for idx, position := range positions {
if colorize || allowFormatting {
result += line[previousPositionEnd:position.Start]
result += matchOutput(line[position.Start:position.End])
result += matchStyle.Render(line[position.Start:position.End])
if idx == len(positions)-1 {
result += line[position.End:]
} else {
+7 -7
View File
@@ -42,20 +42,20 @@ func PrintCanaries(con *console.SliverClient, canaries []*clientpb.DNSCanary, bu
if burnedOnly && !canary.Triggered {
continue
}
lineColor := console.Normal
lineStyle := console.StyleNormal
if canary.Triggered {
lineColor = console.Bold + console.Red
lineStyle = console.StyleBoldRed
}
firstTrigger := "Never"
latestTrigger := "Never"
if canary.Triggered {
firstTrigger = fmt.Sprintf(lineColor+"%s"+console.Normal, canary.FirstTriggered)
latestTrigger = fmt.Sprintf(lineColor+"%s"+console.Normal, canary.LatestTrigger)
firstTrigger = lineStyle.Render(canary.FirstTriggered)
latestTrigger = lineStyle.Render(canary.LatestTrigger)
}
row := table.Row{
fmt.Sprintf(lineColor+"%s"+console.Normal, canary.ImplantName),
fmt.Sprintf(lineColor+"%s"+console.Normal, canary.Domain),
fmt.Sprintf(lineColor+"%v"+console.Normal, canary.Triggered),
lineStyle.Render(canary.ImplantName),
lineStyle.Render(canary.Domain),
lineStyle.Render(fmt.Sprintf("%v", canary.Triggered)),
firstTrigger,
latestTrigger,
}
+4 -4
View File
@@ -15,19 +15,19 @@ func GenerateInfoCmd(cmd *cobra.Command, con *console.SliverClient, args []strin
con.PrintErrorf("Failed to get compiler information: %s\n", err)
return
}
con.Printf("%sServer:%s %s/%s\n", console.Bold, console.Normal, compiler.GOOS, compiler.GOARCH)
con.Printf("%s %s/%s\n", console.StyleBold.Render("Server:"), compiler.GOOS, compiler.GOARCH)
con.Println()
con.Printf("%sCross Compilers%s\n", console.Bold, console.Normal)
con.Printf("%s\n", console.StyleBold.Render("Cross Compilers"))
for _, cc := range compiler.CrossCompilers {
con.Printf("%s/%s - %s\n", cc.TargetGOOS, cc.TargetGOARCH, cc.GetCCPath())
}
con.Println()
con.Printf("%sSupported Targets%s\n", console.Bold, console.Normal)
con.Printf("%s\n", console.StyleBold.Render("Supported Targets"))
for _, target := range compiler.Targets {
con.Printf("%s/%s - %s\n", target.GOOS, target.GOARCH, nameOfOutputFormat(target.Format))
}
con.Println()
con.Printf("%sDefault Builds Only%s\n", console.Bold, console.Normal)
con.Printf("%s\n", console.StyleBold.Render("Default Builds Only"))
for _, target := range compiler.UnsupportedTargets {
con.Printf("%s/%s - %s\n", target.GOOS, target.GOARCH, nameOfOutputFormat(target.Format))
}
+5 -7
View File
@@ -820,9 +820,7 @@ func getTargets(targetOS string, targetArch string, con *console.SliverClient) (
target := fmt.Sprintf("%s/%s", targetOS, targetArch)
if _, ok := SupportedCompilerTargets[target]; !ok {
con.Printf("⚠️ Unsupported compiler target %s%s%s, but we can try to compile a generic implant.\n",
console.Bold, target, console.Normal,
)
con.Printf("⚠️ Unsupported compiler target %s, but we can try to compile a generic implant.\n", console.StyleBold.Render(target))
con.Printf("⚠️ Generic implants do not support all commands/features.\n")
var confirm bool
_ = forms.Confirm("Attempt to build generic implant?", &confirm)
@@ -1172,9 +1170,9 @@ func externalBuild(name string, config *clientpb.ImplantConfig, save string, con
con.PrintInfof("Externally generating new %s/%s implant binary\n", config.GOOS, config.GOARCH)
}
if config.ObfuscateSymbols {
con.PrintInfof("%sSymbol obfuscation is enabled%s\n", console.Bold, console.Normal)
con.PrintInfof("%s\n", console.StyleBold.Render("Symbol obfuscation is enabled"))
} else if !config.Debug {
con.PrintErrorf("Symbol obfuscation is %sdisabled%s\n", console.Bold, console.Normal)
con.PrintErrorf("Symbol obfuscation is %s\n", console.StyleBold.Render("disabled"))
}
start := time.Now()
@@ -1325,9 +1323,9 @@ func compile(name string, config *clientpb.ImplantConfig, save string, con *cons
con.PrintInfof("Generating new %s/%s implant binary\n", config.GOOS, config.GOARCH)
}
if config.ObfuscateSymbols {
con.PrintInfof("%sSymbol obfuscation is enabled%s\n", console.Bold, console.Normal)
con.PrintInfof("%s\n", console.StyleBold.Render("Symbol obfuscation is enabled"))
} else if !config.Debug {
con.PrintErrorf("Symbol obfuscation is %sdisabled%s\n", console.Bold, console.Normal)
con.PrintErrorf("Symbol obfuscation is %s\n", console.StyleBold.Render("disabled"))
}
start := time.Now()
+4 -4
View File
@@ -71,7 +71,7 @@ func DisplayTrafficEncoders(encoderMap *clientpb.TrafficEncoderMap, con *console
allIDs = append(allIDs, encoder.ID)
name := encoder.Wasm.Name
if isDuplicate {
name = fmt.Sprintf(console.Bold+console.Red+"%s (duplicate id)"+console.Normal, name)
name = console.StyleBoldRed.Render(fmt.Sprintf("%s (duplicate id)", name))
}
tw.AppendRow(table.Row{
encoder.ID,
@@ -212,9 +212,9 @@ func displayTrafficEncoderTests(running bool, tests *clientpb.TrafficEncoderTest
for _, test := range tests.Tests {
var success string
if test.Success {
success = console.Bold + console.Green + "Passed" + console.Normal
success = console.StyleBoldGreen.Render("Passed")
} else {
success = console.Bold + console.Red + "Failed!" + console.Normal
success = console.StyleBoldRed.Render("Failed!")
}
errorMsg := "N/A"
if test.Err != "" {
@@ -234,7 +234,7 @@ func displayTrafficEncoderTests(running bool, tests *clientpb.TrafficEncoderTest
if running {
con.Println()
con.Println()
con.Printf(console.Bold+" >>> Running test %d of %d please wait ...\r"+console.Normal, len(tests.Tests), tests.TotalTests)
con.Printf("%s\r", console.StyleBold.Render(fmt.Sprintf(" >>> Running test %d of %d please wait ...", len(tests.Tests), tests.TotalTests)))
lineCount += 2
}
+97 -49
View File
@@ -23,12 +23,12 @@ package help
*/
import (
"bytes"
"fmt"
"strings"
"text/template"
consts "github.com/bishopfox/sliver/client/constants"
"github.com/bishopfox/sliver/client/theme"
"github.com/charmbracelet/lipgloss"
)
const (
@@ -1418,24 +1418,6 @@ On Windows, escaping is disabled. Instead, '\\' is treated as path separator.`
To get information about services, you need to be an authenticated user on the system or domain. To control services, you need administrator or higher privileges.`
)
const (
// ANSI Colors
normal = "\033[0m"
black = "\033[30m"
red = "\033[31m"
green = "\033[32m"
orange = "\033[33m"
blue = "\033[34m"
purple = "\033[35m"
cyan = "\033[36m"
gray = "\033[37m"
bold = "\033[1m"
clearln = "\r\x1b[2K"
upN = "\033[%dA"
downN = "\033[%dB"
underline = "\033[4m"
)
// GetHelpFor - Get help string for a command
func GetHelpFor(cmdName []string) string {
if 0 < len(cmdName) {
@@ -1446,34 +1428,100 @@ func GetHelpFor(cmdName []string) string {
return ""
}
// FormatHelpTmpl - Applies format template to help string
type helpStyleState struct {
bold bool
underline bool
fgSet bool
fg lipgloss.Color
}
func (s *helpStyleState) reset() {
s.bold = false
s.underline = false
s.fgSet = false
s.fg = ""
}
func (s *helpStyleState) apply(token string) bool {
switch token {
case "Normal":
s.reset()
case "Bold":
s.bold = true
case "Underline":
s.underline = true
case "Black":
s.fgSet, s.fg = true, theme.DefaultMod(50)
case "Red":
s.fgSet, s.fg = true, theme.Danger()
case "Green":
s.fgSet, s.fg = true, theme.Success()
case "Orange":
s.fgSet, s.fg = true, theme.Warning()
case "Blue":
s.fgSet, s.fg = true, theme.Primary()
case "Purple":
s.fgSet, s.fg = true, theme.Secondary()
case "Cyan":
s.fgSet, s.fg = true, theme.PrimaryMod(500)
case "Gray":
// ANSI "gray" (37) is typically light; map to the lightest neutral.
s.fgSet, s.fg = true, theme.DefaultMod(900)
default:
return false
}
return true
}
func (s helpStyleState) render(text string) string {
if text == "" {
return ""
}
st := lipgloss.NewStyle()
if s.bold {
st = st.Bold(true)
}
if s.underline {
st = st.Underline(true)
}
if s.fgSet {
st = st.Foreground(s.fg)
}
return st.Render(text)
}
// FormatHelpTmpl - Applies the help markup (e.g. [[.Bold]]...[[.Normal]]) using lipgloss.
func FormatHelpTmpl(helpStr string) string {
outputBuf := bytes.NewBufferString("")
tmpl, _ := template.New("help").Delims("[[", "]]").Parse(helpStr)
tmpl.Execute(outputBuf, struct {
Normal string
Bold string
Underline string
Black string
Red string
Green string
Orange string
Blue string
Purple string
Cyan string
Gray string
}{
Normal: normal,
Bold: bold,
Underline: underline,
Black: black,
Red: red,
Green: green,
Orange: orange,
Blue: blue,
Purple: purple,
Cyan: cyan,
Gray: gray,
})
return outputBuf.String()
var (
out strings.Builder
state helpStyleState
)
state.reset()
i := 0
for i < len(helpStr) {
j := strings.Index(helpStr[i:], "[[.")
if j < 0 {
out.WriteString(state.render(helpStr[i:]))
break
}
j += i
if j > i {
out.WriteString(state.render(helpStr[i:j]))
}
k := strings.Index(helpStr[j:], "]]")
if k < 0 {
out.WriteString(state.render(helpStr[j:]))
break
}
token := helpStr[j+3 : j+k]
if !state.apply(token) {
// Unknown token; preserve it as plain text.
out.WriteString(helpStr[j : j+k+2])
}
i = j + k + 2
}
return out.String()
}
+18 -9
View File
@@ -5,6 +5,7 @@ import (
"strconv"
"strings"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/util"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
@@ -61,13 +62,21 @@ type editorModel struct {
}
var (
cursorStyle = lipgloss.NewStyle().Reverse(true)
modifiedStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("215"))
modifiedCursorStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("215")).Reverse(true)
statusStyle = lipgloss.NewStyle().Reverse(true)
lineStyle = lipgloss.NewStyle()
cursorStyle = lipgloss.NewStyle().Reverse(true)
statusStyle = lipgloss.NewStyle().Reverse(true)
lineStyle = lipgloss.NewStyle()
)
func modifiedStyle() lipgloss.Style {
// Read from the global console theme so theme.yaml changes are reflected.
return console.StyleWarning
}
func modifiedCursorStyle() lipgloss.Style {
// Read from the global console theme so theme.yaml changes are reflected.
return console.StyleWarning.Copy().Reverse(true)
}
func newEditorModel(data []byte, filename string, offset int) *editorModel {
copyData := append([]byte(nil), data...)
model := &editorModel{
@@ -400,8 +409,8 @@ func (m *editorModel) renderLine(lineIndex int) string {
isModified := m.isModified(idx)
if idx == m.cursor {
if isModified {
hexCells[i] = modifiedCursorStyle.Render(hexRaw)
asciiCells[i] = modifiedCursorStyle.Render(asciiRaw)
hexCells[i] = modifiedCursorStyle().Render(hexRaw)
asciiCells[i] = modifiedCursorStyle().Render(asciiRaw)
} else {
hexCells[i] = cursorStyle.Render(hexRaw)
asciiCells[i] = cursorStyle.Render(asciiRaw)
@@ -409,8 +418,8 @@ func (m *editorModel) renderLine(lineIndex int) string {
continue
}
if isModified {
hexCells[i] = modifiedStyle.Render(hexRaw)
asciiCells[i] = modifiedStyle.Render(asciiRaw)
hexCells[i] = modifiedStyle().Render(hexRaw)
asciiCells[i] = modifiedStyle().Render(asciiRaw)
continue
}
hexCells[i] = hexRaw
+38 -38
View File
@@ -70,47 +70,47 @@ func InfoCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
if session != nil {
con.Printf(console.Bold+" Session ID: %s%s\n", console.Normal, session.ID)
con.Printf(console.Bold+" Name: %s%s\n", console.Normal, session.Name)
con.Printf(console.Bold+" Hostname: %s%s\n", console.Normal, session.Hostname)
con.Printf(console.Bold+" UUID: %s%s\n", console.Normal, session.UUID)
con.Printf(console.Bold+" Username: %s%s\n", console.Normal, session.Username)
con.Printf(console.Bold+" UID: %s%s\n", console.Normal, session.UID)
con.Printf(console.Bold+" GID: %s%s\n", console.Normal, session.GID)
con.Printf(console.Bold+" PID: %s%d\n", console.Normal, session.PID)
con.Printf(console.Bold+" OS: %s%s\n", console.Normal, session.OS)
con.Printf(console.Bold+" Version: %s%s\n", console.Normal, session.Version)
con.Printf(console.Bold+" Locale: %s%s\n", console.Normal, session.Locale)
con.Printf(console.Bold+" Arch: %s%s\n", console.Normal, session.Arch)
con.Printf(console.Bold+" Active C2: %s%s\n", console.Normal, session.ActiveC2)
con.Printf(console.Bold+" Remote Address: %s%s\n", console.Normal, session.RemoteAddress)
con.Printf(console.Bold+" Proxy URL: %s%s\n", console.Normal, session.ProxyURL)
con.Printf(console.Bold+"Reconnect Interval: %s%s\n", console.Normal, time.Duration(session.ReconnectInterval).String())
con.Printf(console.Bold+" First Contact: %s%s\n", console.Normal, con.FormatDateDelta(time.Unix(session.FirstContact, 0), true, false))
con.Printf(console.Bold+" Last Checkin: %s%s\n", console.Normal, con.FormatDateDelta(time.Unix(session.LastCheckin, 0), true, false))
con.Printf("%s %s\n", console.StyleBold.Render(" Session ID:"), session.ID)
con.Printf("%s %s\n", console.StyleBold.Render(" Name:"), session.Name)
con.Printf("%s %s\n", console.StyleBold.Render(" Hostname:"), session.Hostname)
con.Printf("%s %s\n", console.StyleBold.Render(" UUID:"), session.UUID)
con.Printf("%s %s\n", console.StyleBold.Render(" Username:"), session.Username)
con.Printf("%s %s\n", console.StyleBold.Render(" UID:"), session.UID)
con.Printf("%s %s\n", console.StyleBold.Render(" GID:"), session.GID)
con.Printf("%s %d\n", console.StyleBold.Render(" PID:"), session.PID)
con.Printf("%s %s\n", console.StyleBold.Render(" OS:"), session.OS)
con.Printf("%s %s\n", console.StyleBold.Render(" Version:"), session.Version)
con.Printf("%s %s\n", console.StyleBold.Render(" Locale:"), session.Locale)
con.Printf("%s %s\n", console.StyleBold.Render(" Arch:"), session.Arch)
con.Printf("%s %s\n", console.StyleBold.Render(" Active C2:"), session.ActiveC2)
con.Printf("%s %s\n", console.StyleBold.Render(" Remote Address:"), session.RemoteAddress)
con.Printf("%s %s\n", console.StyleBold.Render(" Proxy URL:"), session.ProxyURL)
con.Printf("%s %s\n", console.StyleBold.Render("Reconnect Interval:"), time.Duration(session.ReconnectInterval).String())
con.Printf("%s %s\n", console.StyleBold.Render(" First Contact:"), con.FormatDateDelta(time.Unix(session.FirstContact, 0), true, false))
con.Printf("%s %s\n", console.StyleBold.Render(" Last Checkin:"), con.FormatDateDelta(time.Unix(session.LastCheckin, 0), true, false))
} else if beacon != nil {
con.Printf(console.Bold+" Beacon ID: %s%s\n", console.Normal, beacon.ID)
con.Printf(console.Bold+" Name: %s%s\n", console.Normal, beacon.Name)
con.Printf(console.Bold+" Hostname: %s%s\n", console.Normal, beacon.Hostname)
con.Printf(console.Bold+" UUID: %s%s\n", console.Normal, beacon.UUID)
con.Printf(console.Bold+" Username: %s%s\n", console.Normal, beacon.Username)
con.Printf(console.Bold+" UID: %s%s\n", console.Normal, beacon.UID)
con.Printf(console.Bold+" GID: %s%s\n", console.Normal, beacon.GID)
con.Printf(console.Bold+" PID: %s%d\n", console.Normal, beacon.PID)
con.Printf(console.Bold+" OS: %s%s\n", console.Normal, beacon.OS)
con.Printf(console.Bold+" Version: %s%s\n", console.Normal, beacon.Version)
con.Printf(console.Bold+" Locale: %s%s\n", console.Normal, beacon.Locale)
con.Printf(console.Bold+" Arch: %s%s\n", console.Normal, beacon.Arch)
con.Printf(console.Bold+" Active C2: %s%s\n", console.Normal, beacon.ActiveC2)
con.Printf(console.Bold+" Remote Address: %s%s\n", console.Normal, beacon.RemoteAddress)
con.Printf(console.Bold+" Proxy URL: %s%s\n", console.Normal, beacon.ProxyURL)
con.Printf(console.Bold+" Interval: %s%s\n", console.Normal, time.Duration(beacon.Interval).String())
con.Printf(console.Bold+" Jitter: %s%s\n", console.Normal, time.Duration(beacon.Jitter).String())
con.Printf(console.Bold+" First Contact: %s%s\n", console.Normal, con.FormatDateDelta(time.Unix(beacon.FirstContact, 0), true, false))
con.Printf(console.Bold+" Last Checkin: %s%s\n", console.Normal, con.FormatDateDelta(time.Unix(beacon.LastCheckin, 0), true, false))
con.Printf(console.Bold+" Next Checkin: %s%s\n", console.Normal, con.FormatDateDelta(time.Unix(beacon.NextCheckin, 0), true, true))
con.Printf("%s %s\n", console.StyleBold.Render(" Beacon ID:"), beacon.ID)
con.Printf("%s %s\n", console.StyleBold.Render(" Name:"), beacon.Name)
con.Printf("%s %s\n", console.StyleBold.Render(" Hostname:"), beacon.Hostname)
con.Printf("%s %s\n", console.StyleBold.Render(" UUID:"), beacon.UUID)
con.Printf("%s %s\n", console.StyleBold.Render(" Username:"), beacon.Username)
con.Printf("%s %s\n", console.StyleBold.Render(" UID:"), beacon.UID)
con.Printf("%s %s\n", console.StyleBold.Render(" GID:"), beacon.GID)
con.Printf("%s %d\n", console.StyleBold.Render(" PID:"), beacon.PID)
con.Printf("%s %s\n", console.StyleBold.Render(" OS:"), beacon.OS)
con.Printf("%s %s\n", console.StyleBold.Render(" Version:"), beacon.Version)
con.Printf("%s %s\n", console.StyleBold.Render(" Locale:"), beacon.Locale)
con.Printf("%s %s\n", console.StyleBold.Render(" Arch:"), beacon.Arch)
con.Printf("%s %s\n", console.StyleBold.Render(" Active C2:"), beacon.ActiveC2)
con.Printf("%s %s\n", console.StyleBold.Render(" Remote Address:"), beacon.RemoteAddress)
con.Printf("%s %s\n", console.StyleBold.Render(" Proxy URL:"), beacon.ProxyURL)
con.Printf("%s %s\n", console.StyleBold.Render(" Interval:"), time.Duration(beacon.Interval).String())
con.Printf("%s %s\n", console.StyleBold.Render(" Jitter:"), time.Duration(beacon.Jitter).String())
con.Printf("%s %s\n", console.StyleBold.Render(" First Contact:"), con.FormatDateDelta(time.Unix(beacon.FirstContact, 0), true, false))
con.Printf("%s %s\n", console.StyleBold.Render(" Last Checkin:"), con.FormatDateDelta(time.Unix(beacon.LastCheckin, 0), true, false))
con.Printf("%s %s\n", console.StyleBold.Render(" Next Checkin:"), con.FormatDateDelta(time.Unix(beacon.NextCheckin, 0), true, true))
} else {
con.PrintErrorf("No target session, see `help %s`\n", consts.InfoStr)
+1 -1
View File
@@ -82,7 +82,7 @@ func PrintLootFile(loot *clientpb.Loot, con *console.SliverClient) {
return
}
if loot.File.Name != "" {
con.PrintInfof("%sFile Name:%s %s\n\n", console.Bold, console.Normal, loot.File.Name)
con.PrintInfof("%s %s\n\n", console.StyleBold.Render("File Name:"), loot.File.Name)
}
if loot.File.Data != nil && 0 < len(loot.File.Data) {
if loot.FileType == clientpb.FileType_TEXT || isText(loot.File.Data) {
+4 -7
View File
@@ -20,7 +20,6 @@ package monitor
import (
"context"
"fmt"
"github.com/bishopfox/sliver/client/command/settings"
"github.com/bishopfox/sliver/client/console"
@@ -99,14 +98,12 @@ func PrintWTConfig(configs *clientpb.MonitoringProviders, con *console.SliverCli
"APIPassword",
})
color := console.Normal
for _, config := range configs.Providers {
row := table.Row{}
row = append(row, fmt.Sprintf(color+"%s"+console.Normal, config.ID))
row = append(row, fmt.Sprintf(color+"%s"+console.Normal, config.Type))
row = append(row, fmt.Sprintf(color+"%s"+console.Normal, config.APIKey))
row = append(row, fmt.Sprintf(color+"%s"+console.Normal, config.APIPassword))
row = append(row, config.ID)
row = append(row, config.Type)
row = append(row, config.APIKey)
row = append(row, config.APIPassword)
tw.AppendRow(row)
}
+2 -2
View File
@@ -75,7 +75,7 @@ func PrintIfconfig(ifconfig *sliverpb.Ifconfig, all bool, con *console.SliverCli
for index, iface := range interfaces {
tw := table.NewWriter()
tw.SetStyle(settings.GetTableWithBordersStyle(con))
tw.SetTitle(fmt.Sprintf(console.Bold+"%s"+console.Normal, iface.Name))
tw.SetTitle(console.StyleBold.Render(iface.Name))
tw.SetColumnConfigs([]table.ColumnConfig{
{Name: "#", AutoMerge: true},
{Name: "IP Address", AutoMerge: true},
@@ -100,7 +100,7 @@ func PrintIfconfig(ifconfig *sliverpb.Ifconfig, all bool, con *console.SliverCli
}
}
if 0 < subnet && subnet <= 32 && !isLoopback(ip) {
ips = append(ips, fmt.Sprintf(console.Bold+console.Green+"%s"+console.Normal, ip))
ips = append(ips, console.StyleBoldGreen.Render(ip))
} else if all {
ips = append(ips, fmt.Sprintf("%s", ip))
}
+5 -5
View File
@@ -104,11 +104,11 @@ func PrintNetstat(netstat *sliverpb.Netstat, implantPID int32, activeC2 string,
}
if entry.Process != nil && entry.Process.Pid == implantPID {
tw.AppendRow(table.Row{
fmt.Sprintf(console.Green+"%s"+console.Normal, entry.Protocol),
fmt.Sprintf(console.Green+"%s"+console.Normal, srcAddr),
fmt.Sprintf(console.Green+"%s"+console.Normal, dstAddr),
fmt.Sprintf(console.Green+"%s"+console.Normal, entry.SkState),
fmt.Sprintf(console.Green+"%s"+console.Normal, pid),
console.StyleGreen.Render(entry.Protocol),
console.StyleGreen.Render(srcAddr),
console.StyleGreen.Render(dstAddr),
console.StyleGreen.Render(entry.SkState),
console.StyleGreen.Render(pid),
})
} else {
tw.AppendRow(table.Row{entry.Protocol, srcAddr, dstAddr, entry.SkState, pid})
+3 -3
View File
@@ -50,7 +50,7 @@ func displayOperators(operators []*clientpb.Operator, con *console.SliverClient)
})
for _, operator := range operators {
tw.AppendRow(table.Row{
console.Bold + operator.Name + console.Normal,
console.StyleBold.Render(operator.Name),
status(operator.Online),
})
}
@@ -59,7 +59,7 @@ func displayOperators(operators []*clientpb.Operator, con *console.SliverClient)
func status(isOnline bool) string {
if isOnline {
return console.Bold + console.Green + "Online" + console.Normal
return console.StyleBoldGreen.Render("Online")
}
return console.Bold + console.Red + "Offline" + console.Normal
return console.StyleBoldRed.Render("Offline")
}
+1 -1
View File
@@ -81,7 +81,7 @@ func PrintPivotListenerDetails(listener *sliverpb.PivotListener, con *console.Sl
tw := table.NewWriter()
tw.SetStyle(settings.GetTableStyle(con))
tw.SetTitle(fmt.Sprintf(console.Bold+"%s Pivots"+console.Normal, PivotTypeToString(listener.Type)))
tw.SetTitle(console.StyleBold.Render(fmt.Sprintf("%s Pivots", PivotTypeToString(listener.Type))))
tw.AppendSeparator()
tw.AppendHeader(table.Row{
"ID",
+107 -107
View File
@@ -36,77 +36,77 @@ import (
"github.com/bishopfox/sliver/protobuf/sliverpb"
)
// Stylizes known processes in the `ps` command
var knownSecurityTools = map[string][]string{
// Process Name -> [Color, Stylized Name]
"ccSvcHst.exe": {console.Red, "Symantec Endpoint Protection"}, // Symantec Endpoint Protection (SEP)
"cb.exe": {console.Red, "Carbon Black"}, // Carbon Black
"RepMgr.exe": {console.Red, "Carbon Black Cloud Sensor"}, // Carbon Black Cloud Sensor
"RepUtils.exe": {console.Red, "Carbon Black Cloud Sensor"}, // Carbon Black Cloud Sensor
"RepUx.exe": {console.Red, "Carbon Black Cloud Sensor"}, // Carbon Black Cloud Sensor
"RepWSC.exe": {console.Red, "Carbon Black Cloud Sensor"}, // Carbon Black Cloud Sensor
"scanhost.exe": {console.Red, "Carbon Black Cloud Sensor"}, // Carbon Black Cloud Sensor
"elastic-agent.exe": {console.Red, "Elastic Agent"}, // Elastic Agent
"elastic-endpoint.exe": {console.Red, "Elastic Agent"}, // Elastic Agent
"filebeat.exe": {console.Red, "Elastic Agent"}, // Elastic Agent - log shipper
"metricbeat.exe": {console.Red, "Elastic Agent"}, // Elastic Agent - metric shipper
"smartscreen.exe": {console.Red, "Windows Smart Screen"}, // Windows Defender Smart Screen
"MpCmdRun.exe": {console.Red, "Windows Defender"}, // Windows Defender Command-line
"MonitoringHost.exe": {console.Red, "Windows Defender"}, // Microsoft Monitoring Agent
"HealthService.exe": {console.Red, "Windows Defender"}, // Microsoft Monitoring Agent
"MsMpEng.exe": {console.Red, "Windows Defender"}, // Windows Defender (Service Executable)
"NisSrv.exe": {console.Red, "Windows Defender"}, // Windows Defender (Network Realtime Inspection)
"SenseIR.exe": {console.Red, "Windows Defender MDE"}, // Windows Defender Endpoint (Live Response Session)
"SenseNdr.exe": {console.Red, "Windows Defender MDE"}, // Windows Defender Endpoint (Network Detection and Response)
"SenseSC.exe": {console.Red, "Windows Defender MDE"}, // Windows Defender Endpoint (Screenshot Capture Module)
"SenseCE.exe": {console.Red, "Windows Defender MDE"}, // Windows Defender Endpoint (Classification Engine Module)
"SenseCM.exe": {console.Red, "Windows Defender MDE"}, // Windows Defender Endpoint (Configuration Management Module)
"SenseSampleUploader.exe": {console.Red, "Windows Defender MDE"}, // Windows Defender Endpoint (Sample Uploader Module)
"SenseCncProxy.exe": {console.Red, "Windows Defender MDE"}, // Windows Defender Endpoint (Communication Module)
"MsSense.exe": {console.Red, "Windows Defender MDE"}, // Windows Defender Endpoint (Service Executable)
"CSFalconService.exe": {console.Red, "CrowdStrike"}, // Crowdstrike Falcon Service
"CSFalconContainer.exe": {console.Red, "CrowdStrike"}, // CrowdStrike Falcon Container Security
"bdservicehost.exe": {console.Red, "Bitdefender"}, // Bitdefender (Total Security)
"bdagent.exe": {console.Red, "Bitdefender"}, // Bitdefender (Total Security)
"bdredline.exe": {console.Red, "Bitdefender"}, // Bitdefender Redline Update Service (Source https://community.bitdefender.com/en/discussion/82135/bdredline-exe-bitdefender-total-security-2020)
"Deep Security Manager.exe": {console.Red, "Trend Micro"}, // TM Deep Security Manager
"coreServiceShell.exe": {console.Red, "Trend Micro"}, // TM Anti-malware scan process
"ds_monitor.exe": {console.Red, "Trend Micro"}, // TM Deep Security Monitor
"Notifier.exe": {console.Red, "Trend Micro"}, // TM Deep Security Notifier's process
"dsa.exe": {console.Red, "Trend Micro"}, // TM Agent's main process
"ds_nuagent.exe": {console.Red, "Trend Micro"}, // TM Advanced TLS traffic inspection
"coreFrameworkHost.exe": {console.Red, "Trend Micro"}, // TM Anti-malware scan process
"SentinelServiceHost.exe": {console.Red, "SentinelOne"}, // Sentinel One
"SentinelStaticEngine.exe": {console.Red, "SentinelOne"}, // Sentinel One
"SentinelStaticEngineScanner.exe": {console.Red, "SentinelOne"}, // Sentinel One
"SentinelAgent.exe": {console.Red, "SentinelOne"}, // Sentinel One
"SentinelAgentWorker.exe": {console.Red, "SentinelOne"}, // Sentinel One
"SentinelHelperService.exe": {console.Red, "SentinelOne"}, // Sentinel One
"SentinelBrowserNativeHost.exe": {console.Red, "SentinelOne"}, // Sentinel One
"SentinelUI.exe": {console.Red, "SentinelOne"}, // Sentinel One
"Sysmon.exe": {console.Red, "Sysmon"}, // Sysmon
"Sysmon64.exe": {console.Red, "Sysmon64"}, // Sysmon64
"CylanceSvc.exe": {console.Red, "Cylance"}, // Cylance
"CylanceUI.exe": {console.Red, "Cylance"}, // Cylance
"TaniumClient.exe": {console.Red, "Tanium"}, // Tanium
"TaniumCX.exe": {console.Red, "Tanium"}, // Tanium
"TaniumDetectEngine.exe": {console.Red, "Tanium"}, // Tanium
"collector.exe": {console.Red, "Rapid 7 Collector"}, // Rapid 7 Insight Platform Collector
"ir_agent.exe": {console.Red, "Rapid 7 Insight Agent"}, // Rapid 7 Insight Agent
"eguiproxy.exe": {console.Red, "ESET Security"}, // ESET Internet Security
"ekrn.exe": {console.Red, "ESET Security"}, // ESET Internet Security
"efwd.exe": {console.Red, "ESET Security"}, // ESET Internet Security
"AmSvc.exe": {console.Red, "Cybereason ActiveProbe"}, // Cybereason ActiveProbe
"CrAmTray.exe": {console.Red, "Cybereason ActiveProbe"}, // Cybereason ActiveProbe
"CrsSvc.exe": {console.Red, "Cybereason ActiveProbe"}, // Cybereason ActiveProbe
"CybereasonAV.exe": {console.Red, "Cybereason ActiveProbe"}, // Cybereason ActiveProbe
"cortex-xdr-payload.exe": {console.Red, "Palo Alto Cortex"}, // Cortex XDR - offline triage
"cysandbox.exe": {console.Red, "Palo Alto Cortex"}, // Cortex XDR - sandbox
"cyuserservice.exe": {console.Red, "Palo Alto Cortex"}, // Cortex XDR - user service
"cywscsvc.exe": {console.Red, "Palo Alto Cortex"}, // Cortex XDR - security center service
"tlaworker.exe": {console.Red, "Palo Alto Cortex"}, // Cortex XDR - local analysis worker
"AEEngine.exe": {console.Red, "Faronics Anti-Executable"}, // Faronics Anti-Executable - security service
"Antiexecutable.exe": {console.Red, "Faronics Anti-Executable"}, // Faronics Anti-Executable - gui and tray icon
// Known security tools shown by `ps`/`pstree`.
// Process executable -> product name.
var knownSecurityTools = map[string]string{
"ccSvcHst.exe": "Symantec Endpoint Protection", // Symantec Endpoint Protection (SEP)
"cb.exe": "Carbon Black", // Carbon Black
"RepMgr.exe": "Carbon Black Cloud Sensor", // Carbon Black Cloud Sensor
"RepUtils.exe": "Carbon Black Cloud Sensor", // Carbon Black Cloud Sensor
"RepUx.exe": "Carbon Black Cloud Sensor", // Carbon Black Cloud Sensor
"RepWSC.exe": "Carbon Black Cloud Sensor", // Carbon Black Cloud Sensor
"scanhost.exe": "Carbon Black Cloud Sensor", // Carbon Black Cloud Sensor
"elastic-agent.exe": "Elastic Agent", // Elastic Agent
"elastic-endpoint.exe": "Elastic Agent", // Elastic Agent
"filebeat.exe": "Elastic Agent", // Elastic Agent - log shipper
"metricbeat.exe": "Elastic Agent", // Elastic Agent - metric shipper
"smartscreen.exe": "Windows Smart Screen", // Windows Defender Smart Screen
"MpCmdRun.exe": "Windows Defender", // Windows Defender Command-line
"MonitoringHost.exe": "Windows Defender", // Microsoft Monitoring Agent
"HealthService.exe": "Windows Defender", // Microsoft Monitoring Agent
"MsMpEng.exe": "Windows Defender", // Windows Defender (Service Executable)
"NisSrv.exe": "Windows Defender", // Windows Defender (Network Realtime Inspection)
"SenseIR.exe": "Windows Defender MDE", // Windows Defender Endpoint (Live Response Session)
"SenseNdr.exe": "Windows Defender MDE", // Windows Defender Endpoint (Network Detection and Response)
"SenseSC.exe": "Windows Defender MDE", // Windows Defender Endpoint (Screenshot Capture Module)
"SenseCE.exe": "Windows Defender MDE", // Windows Defender Endpoint (Classification Engine Module)
"SenseCM.exe": "Windows Defender MDE", // Windows Defender Endpoint (Configuration Management Module)
"SenseSampleUploader.exe": "Windows Defender MDE", // Windows Defender Endpoint (Sample Uploader Module)
"SenseCncProxy.exe": "Windows Defender MDE", // Windows Defender Endpoint (Communication Module)
"MsSense.exe": "Windows Defender MDE", // Windows Defender Endpoint (Service Executable)
"CSFalconService.exe": "CrowdStrike", // Crowdstrike Falcon Service
"CSFalconContainer.exe": "CrowdStrike", // CrowdStrike Falcon Container Security
"bdservicehost.exe": "Bitdefender", // Bitdefender (Total Security)
"bdagent.exe": "Bitdefender", // Bitdefender (Total Security)
"bdredline.exe": "Bitdefender", // Bitdefender Redline Update Service (Source https://community.bitdefender.com/en/discussion/82135/bdredline-exe-bitdefender-total-security-2020)
"Deep Security Manager.exe": "Trend Micro", // TM Deep Security Manager
"coreServiceShell.exe": "Trend Micro", // TM Anti-malware scan process
"ds_monitor.exe": "Trend Micro", // TM Deep Security Monitor
"Notifier.exe": "Trend Micro", // TM Deep Security Notifier's process
"dsa.exe": "Trend Micro", // TM Agent's main process
"ds_nuagent.exe": "Trend Micro", // TM Advanced TLS traffic inspection
"coreFrameworkHost.exe": "Trend Micro", // TM Anti-malware scan process
"SentinelServiceHost.exe": "SentinelOne", // Sentinel One
"SentinelStaticEngine.exe": "SentinelOne", // Sentinel One
"SentinelStaticEngineScanner.exe": "SentinelOne", // Sentinel One
"SentinelAgent.exe": "SentinelOne", // Sentinel One
"SentinelAgentWorker.exe": "SentinelOne", // Sentinel One
"SentinelHelperService.exe": "SentinelOne", // Sentinel One
"SentinelBrowserNativeHost.exe": "SentinelOne", // Sentinel One
"SentinelUI.exe": "SentinelOne", // Sentinel One
"Sysmon.exe": "Sysmon", // Sysmon
"Sysmon64.exe": "Sysmon64", // Sysmon64
"CylanceSvc.exe": "Cylance", // Cylance
"CylanceUI.exe": "Cylance", // Cylance
"TaniumClient.exe": "Tanium", // Tanium
"TaniumCX.exe": "Tanium", // Tanium
"TaniumDetectEngine.exe": "Tanium", // Tanium
"collector.exe": "Rapid 7 Collector", // Rapid 7 Insight Platform Collector
"ir_agent.exe": "Rapid 7 Insight Agent", // Rapid 7 Insight Agent
"eguiproxy.exe": "ESET Security", // ESET Internet Security
"ekrn.exe": "ESET Security", // ESET Internet Security
"efwd.exe": "ESET Security", // ESET Internet Security
"AmSvc.exe": "Cybereason ActiveProbe", // Cybereason ActiveProbe
"CrAmTray.exe": "Cybereason ActiveProbe", // Cybereason ActiveProbe
"CrsSvc.exe": "Cybereason ActiveProbe", // Cybereason ActiveProbe
"CybereasonAV.exe": "Cybereason ActiveProbe", // Cybereason ActiveProbe
"cortex-xdr-payload.exe": "Palo Alto Cortex", // Cortex XDR - offline triage
"cysandbox.exe": "Palo Alto Cortex", // Cortex XDR - sandbox
"cyuserservice.exe": "Palo Alto Cortex", // Cortex XDR - user service
"cywscsvc.exe": "Palo Alto Cortex", // Cortex XDR - security center service
"tlaworker.exe": "Palo Alto Cortex", // Cortex XDR - local analysis worker
"AEEngine.exe": "Faronics Anti-Executable", // Faronics Anti-Executable - security service
"Antiexecutable.exe": "Faronics Anti-Executable", // Faronics Anti-Executable - gui and tray icon
}
// PsCmd - List processes on the remote system
@@ -264,10 +264,10 @@ func PrintPS(os string, ps *sliverpb.Ps, interactive bool, fullInfo bool, flags
}
func findKnownSecurityProducts(ps *sliverpb.Ps) []string {
uniqProducts := map[string]string{}
uniqProducts := map[string]struct{}{}
for _, proc := range ps.Processes {
if secTool, ok := knownSecurityTools[proc.Executable]; ok {
uniqProducts[secTool[1]] = secTool[0]
if product, ok := knownSecurityTools[proc.Executable]; ok {
uniqProducts[product] = struct{}{}
}
}
products := make([]string, 0, len(uniqProducts))
@@ -281,15 +281,15 @@ func findKnownSecurityProducts(ps *sliverpb.Ps) []string {
func procRow(proc *commonpb.Process, cmdLine bool, fullInfo bool, con *console.SliverClient) table.Row {
session, beacon := con.ActiveTarget.GetInteractive()
color := console.Normal
if secTool, ok := knownSecurityTools[proc.Executable]; ok {
color = secTool[0]
style := console.StyleNormal
if _, ok := knownSecurityTools[proc.Executable]; ok {
style = console.StyleRed
}
if session != nil && proc.Pid == session.PID {
color = console.Green
style = console.StyleGreen
}
if beacon != nil && proc.Pid == beacon.PID {
color = console.Green
style = console.StyleGreen
}
var row table.Row
@@ -303,28 +303,28 @@ func procRow(proc *commonpb.Process, cmdLine bool, fullInfo bool, con *console.S
args = proc.Executable
}
row = table.Row{
fmt.Sprintf(color+"%d"+console.Normal, proc.Pid),
fmt.Sprintf(color+"%d"+console.Normal, proc.Ppid),
fmt.Sprintf(color+"%s"+console.Normal, proc.Owner),
fmt.Sprintf(color+"%s"+console.Normal, proc.Architecture),
fmt.Sprintf(color+"%s"+console.Normal, args),
fmt.Sprintf(color+"%d"+console.Normal, proc.SessionID),
style.Render(fmt.Sprintf("%d", proc.Pid)),
style.Render(fmt.Sprintf("%d", proc.Ppid)),
style.Render(proc.Owner),
style.Render(proc.Architecture),
style.Render(args),
style.Render(fmt.Sprintf("%d", proc.SessionID)),
}
} else {
if fullInfo {
row = table.Row{
fmt.Sprintf(color+"%d"+console.Normal, proc.Pid),
fmt.Sprintf(color+"%d"+console.Normal, proc.Ppid),
fmt.Sprintf(color+"%s"+console.Normal, proc.Owner),
fmt.Sprintf(color+"%s"+console.Normal, proc.Architecture),
fmt.Sprintf(color+"%s"+console.Normal, proc.Executable),
fmt.Sprintf(color+"%d"+console.Normal, proc.SessionID),
style.Render(fmt.Sprintf("%d", proc.Pid)),
style.Render(fmt.Sprintf("%d", proc.Ppid)),
style.Render(proc.Owner),
style.Render(proc.Architecture),
style.Render(proc.Executable),
style.Render(fmt.Sprintf("%d", proc.SessionID)),
}
} else {
row = table.Row{
fmt.Sprintf(color+"%d"+console.Normal, proc.Pid),
fmt.Sprintf(color+"%d"+console.Normal, proc.Ppid),
fmt.Sprintf(color+"%s"+console.Normal, proc.Executable),
style.Render(fmt.Sprintf("%d", proc.Pid)),
style.Render(fmt.Sprintf("%d", proc.Ppid)),
style.Render(proc.Executable),
}
}
}
@@ -341,26 +341,26 @@ func procRow(proc *commonpb.Process, cmdLine bool, fullInfo bool, con *console.S
args = proc.Executable
}
row = table.Row{
fmt.Sprintf(color+"%d"+console.Normal, proc.Pid),
fmt.Sprintf(color+"%d"+console.Normal, proc.Ppid),
fmt.Sprintf(color+"%s"+console.Normal, proc.Owner),
fmt.Sprintf(color+"%s"+console.Normal, proc.Architecture),
fmt.Sprintf(color+"%s"+console.Normal, args),
style.Render(fmt.Sprintf("%d", proc.Pid)),
style.Render(fmt.Sprintf("%d", proc.Ppid)),
style.Render(proc.Owner),
style.Render(proc.Architecture),
style.Render(args),
}
} else {
if fullInfo {
row = table.Row{
fmt.Sprintf(color+"%d"+console.Normal, proc.Pid),
fmt.Sprintf(color+"%d"+console.Normal, proc.Ppid),
fmt.Sprintf(color+"%s"+console.Normal, proc.Owner),
fmt.Sprintf(color+"%s"+console.Normal, proc.Architecture),
fmt.Sprintf(color+"%s"+console.Normal, proc.Executable),
style.Render(fmt.Sprintf("%d", proc.Pid)),
style.Render(fmt.Sprintf("%d", proc.Ppid)),
style.Render(proc.Owner),
style.Render(proc.Architecture),
style.Render(proc.Executable),
}
} else {
row = table.Row{
fmt.Sprintf(color+"%d"+console.Normal, proc.Pid),
fmt.Sprintf(color+"%d"+console.Normal, proc.Ppid),
fmt.Sprintf(color+"%s"+console.Normal, proc.Executable),
style.Render(fmt.Sprintf("%d", proc.Pid)),
style.Render(fmt.Sprintf("%d", proc.Ppid)),
style.Render(proc.Executable),
}
}
}
+5 -6
View File
@@ -1,7 +1,6 @@
package processes
import (
"fmt"
"sort"
"github.com/xlab/treeprint"
@@ -108,14 +107,14 @@ func (t *PsTree) AddProcess(proc *commonpb.Process) {
}
func (t *PsTree) filterProc(proc *commonpb.Process) string {
color := console.Normal
style := console.StyleNormal
if proc.Pid == t.implantPID {
color = console.Green
style = console.StyleGreen
}
if secTool, ok := knownSecurityTools[proc.Executable]; ok {
color = secTool[0]
if _, ok := knownSecurityTools[proc.Executable]; ok {
style = console.StyleRed
}
return fmt.Sprintf(color+"%s"+console.Normal, proc.Executable)
return style.Render(proc.Executable)
}
func (t *PsTree) String() string {
+1 -2
View File
@@ -19,7 +19,6 @@ package reaction
*/
import (
"fmt"
"strings"
"github.com/bishopfox/sliver/client/command/settings"
@@ -51,7 +50,7 @@ func ReactionCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
func displayReactionsTable(eventType string, reactions []core.Reaction, con *console.SliverClient) {
tw := table.NewWriter()
tw.SetStyle(settings.GetTableStyle(con))
tw.SetTitle(fmt.Sprintf(console.Bold+"%s"+console.Normal, EventTypeToTitle(eventType)))
tw.SetTitle(console.StyleBold.Render(EventTypeToTitle(eventType)))
tw.AppendSeparator()
slackSpace := len(EventTypeToTitle(eventType)) - len("Commands") - len("ID") - 3
if slackSpace < 0 {
+1
View File
@@ -26,6 +26,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
f.StringP("beacon-interval", "i", "", "beacon callback interval")
f.StringP("beacon-jitter", "j", "", "beacon callback jitter (random up to)")
f.Int64P("timeout", "t", flags.DefaultTimeout, "grpc timeout in seconds")
f.StringP("c2-uri", "c", "", "C2 URI to switch to")
})
renameCmd := &cobra.Command{
+39
View File
@@ -20,6 +20,7 @@ package reconfig
import (
"context"
"net/url"
"time"
"github.com/bishopfox/sliver/client/console"
@@ -52,6 +53,7 @@ func ReconfigCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
var beaconJitter time.Duration
binterval, _ := cmd.Flags().GetString("beacon-interval")
bjitter, _ := cmd.Flags().GetString("beacon-jitter")
C2URI, _ := cmd.Flags().GetString("c2-uri")
if beacon != nil {
if binterval != "" {
@@ -73,10 +75,47 @@ func ReconfigCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
}
}
if C2URI != "" {
// validate uri
newURI, err := url.Parse(C2URI)
if err != nil || newURI.Scheme == "" || newURI.Host == "" {
con.PrintErrorf("Invalid C2 URI: %s (expected format: protocol://host:port)\n", C2URI)
return
}
// validate the protocol is the same as the beacon (cuz it compiled with only one protocol)
var activeC2 string
if beacon != nil {
activeC2 = beacon.ActiveC2
} else if session != nil {
activeC2 = session.ActiveC2
}
if activeC2 != "" {
currentURI, err := url.Parse(activeC2)
if err == nil {
// must be the same protocol
if newURI.Scheme != currentURI.Scheme {
con.PrintErrorf("Cannot switch protocol from %s to %s (protocol must match compiled implant)\n",
currentURI.Scheme, newURI.Scheme)
return
}
// for now only support the same Host/C2 Server (becuase the implant crypto keys problem)
if newURI.Hostname() != currentURI.Hostname() {
con.PrintWarnf("Switching to a different host (%s -> %s). This only works if both servers share the same crypto keys.\n",
currentURI.Hostname(), newURI.Hostname())
}
}
}
con.PrintInfof("Updating C2 endpoint to: %s\n", C2URI)
}
reconfig, err := con.Rpc.Reconfigure(context.Background(), &sliverpb.ReconfigureReq{
ReconnectInterval: int64(reconnectInterval),
BeaconInterval: int64(beaconInterval),
BeaconJitter: int64(beaconJitter),
C2URI: C2URI,
Request: con.ActiveTarget.Request(cmd),
})
if err != nil {
+2
View File
@@ -44,6 +44,7 @@ import (
"github.com/bishopfox/sliver/client/command/monitor"
"github.com/bishopfox/sliver/client/command/operators"
"github.com/bishopfox/sliver/client/command/reaction"
"github.com/bishopfox/sliver/client/command/serverctx"
"github.com/bishopfox/sliver/client/command/sessions"
"github.com/bishopfox/sliver/client/command/settings"
shellcodeencoders "github.com/bishopfox/sliver/client/command/shellcode-encoders"
@@ -97,6 +98,7 @@ func ServerCommands(con *client.SliverClient, serverCmds func() []*cobra.Command
// Core
bind(consts.GenericHelpGroup,
exit.Command,
serverctx.Commands,
licenses.Commands,
settings.Commands,
alias.Commands,
+609
View File
@@ -0,0 +1,609 @@
//go:build client
package serverctx
import (
"context"
"crypto/sha256"
"crypto/x509"
"encoding/pem"
"fmt"
"sort"
"strings"
"sync"
"time"
"github.com/bishopfox/sliver/client/assets"
"github.com/bishopfox/sliver/client/console"
clienttheme "github.com/bishopfox/sliver/client/theme"
"github.com/bishopfox/sliver/client/transport"
"github.com/bishopfox/sliver/protobuf/commonpb"
"github.com/charmbracelet/bubbles/key"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/huh"
"github.com/charmbracelet/lipgloss"
"github.com/spf13/cobra"
"google.golang.org/grpc/connectivity"
)
// Commands returns client-only server connection context commands.
func Commands(con *console.SliverClient) []*cobra.Command {
root := &cobra.Command{
Use: "server",
Short: "Show and manage the current server connection",
Run: func(cmd *cobra.Command, args []string) {
serverInfo(cmd, con)
},
}
switchCmd := &cobra.Command{
Use: "switch",
Short: "Switch to another server/operator profile",
Run: func(cmd *cobra.Command, args []string) {
serverSwitch(cmd, con)
},
}
root.AddCommand(switchCmd)
return []*cobra.Command{root}
}
func serverInfo(_ *cobra.Command, con *console.SliverClient) {
details, state, ok := con.CurrentConnection()
if !ok || con.Rpc == nil {
con.PrintErrorf("Not connected\n")
return
}
var hostPort string
operator := ""
fingerprint := ""
if details != nil && details.Config != nil {
hostPort = fmt.Sprintf("%s:%d", details.Config.LHost, details.Config.LPort)
operator = operatorFromConfig(details.Config)
fingerprint = shortCertFingerprint(details.Config)
}
if operator == "" {
operator = "<unknown>"
}
if hostPort == "" {
hostPort = "<unknown>"
}
ver, err := con.Rpc.GetVersion(context.Background(), &commonpb.Empty{})
if err != nil {
con.PrintErrorf("GetVersion failed: %s\n", err)
return
}
const keyWidth = 8 // Align on ": " (e.g., "Operator", "Version")
label := func(key string) string {
// Bold "default/white" label keys, padded so colons align.
return console.StyleBoldGray.Render(fmt.Sprintf("%-*s:", keyWidth, key))
}
valueSecondary := func(s string) string { return console.StylePurple.Render(s) }
valueDim := func(s string) string { return console.StyleGray.Render(s) }
stateStyle := console.StyleGray
switch state {
case connectivity.Ready:
stateStyle = console.StyleGreen
case connectivity.Connecting:
stateStyle = console.StyleOrange
case connectivity.TransientFailure, connectivity.Shutdown:
stateStyle = console.StyleRed
case connectivity.Idle:
stateStyle = console.StyleOrange
}
// Server/operator values should be "default" terminal text (not themed colorized).
con.Printf("%s %s\n", label("Server"), hostPort)
if details != nil && details.ConfigKey != "" {
con.Printf("%s %s\n", label("Profile"), valueSecondary(details.ConfigKey))
}
con.Printf("%s %s\n", label("Operator"), operator)
if fingerprint != "" {
con.Printf("%s %s\n", label("Cert"), valueDim(fingerprint))
}
con.Printf("%s %s\n", label("gRPC"), stateStyle.Render(state.String()))
con.Printf("%s %s %s\n",
label("Version"),
console.StyleBold.Render(fmt.Sprintf("%d.%d.%d", ver.Major, ver.Minor, ver.Patch)),
valueDim("("+ver.Commit+")"),
)
}
func serverSwitch(_ *cobra.Command, con *console.SliverClient) {
configs := assets.GetConfigs()
if len(configs) == 0 {
con.PrintErrorf("No config files found at %s\n", assets.GetConfigDir())
return
}
keys := make([]string, 0, len(configs))
for k := range configs {
keys = append(keys, k)
}
sort.Strings(keys)
mgr := getConsoleManager(con)
mgr.syncActiveFromConsole(con)
instances, activeID := mgr.snapshot()
result, err := runSwitchTUI(instances, activeID, keys, mgr.configInstanceCountsLocked(instances))
if err != nil {
con.PrintErrorf("TUI error: %s\n", err)
return
}
if result.Action == actionNone {
return
}
switch result.Action {
case actionSwitchExisting:
inst := mgr.findInstance(result.ExistingID)
if inst == nil || inst.Config == nil {
con.PrintErrorf("Invalid selection\n")
return
}
if inst.ID == activeID {
con.PrintInfof("Already on %s\n", inst.DisplayName())
return
}
rpc, conn, err := transport.MTLSConnect(inst.Config)
if err != nil {
con.PrintErrorf("Connection failed: %s\n", err)
return
}
if err := con.SetConnection(rpc, conn, &console.ConnectionDetails{ConfigKey: inst.ConfigKey, Config: inst.Config}); err != nil {
_ = conn.Close()
con.PrintErrorf("Switch failed: %s\n", err)
return
}
mgr.setActive(inst.ID)
con.PrintSuccessf("Switched to %s:%d (%s)\n", inst.Config.LHost, inst.Config.LPort, inst.DisplayName())
case actionNewConsole:
cfg := configs[result.ConfigKey]
if cfg == nil {
con.PrintErrorf("Invalid selection\n")
return
}
rpc, conn, err := transport.MTLSConnect(cfg)
if err != nil {
con.PrintErrorf("Connection failed: %s\n", err)
return
}
if err := con.SetConnection(rpc, conn, &console.ConnectionDetails{ConfigKey: result.ConfigKey, Config: cfg}); err != nil {
_ = conn.Close()
con.PrintErrorf("Switch failed: %s\n", err)
return
}
inst := mgr.addInstance(result.ConfigKey, cfg)
con.PrintSuccessf("Connected to %s:%d (%s)\n", cfg.LHost, cfg.LPort, inst.DisplayName())
}
}
var consoleManagers sync.Map // *console.SliverClient -> *consoleManager
type consoleManager struct {
mu sync.Mutex
nextID int
activeID string
insts []*consoleInstance
}
type consoleInstance struct {
ID string
ConfigKey string
Config *assets.ClientConfig
CreatedAt time.Time
LastUsed time.Time
}
func (i *consoleInstance) DisplayName() string {
if i == nil {
return ""
}
return fmt.Sprintf("console-%s", i.ID)
}
func getConsoleManager(con *console.SliverClient) *consoleManager {
if con == nil {
return &consoleManager{}
}
if v, ok := consoleManagers.Load(con); ok {
return v.(*consoleManager)
}
m := &consoleManager{}
actual, _ := consoleManagers.LoadOrStore(con, m)
return actual.(*consoleManager)
}
func (m *consoleManager) syncActiveFromConsole(con *console.SliverClient) {
if m == nil || con == nil {
return
}
details, _, ok := con.CurrentConnection()
if !ok || details == nil || details.Config == nil || details.ConfigKey == "" {
return
}
m.mu.Lock()
defer m.mu.Unlock()
// If we already have an active instance, keep it. Otherwise create one from the current connection.
if m.activeID != "" {
return
}
m.nextID++
id := fmt.Sprintf("%d", m.nextID)
now := time.Now()
m.activeID = id
m.insts = append(m.insts, &consoleInstance{
ID: id,
ConfigKey: details.ConfigKey,
Config: details.Config,
CreatedAt: now,
LastUsed: now,
})
}
func (m *consoleManager) snapshot() ([]*consoleInstance, string) {
m.mu.Lock()
defer m.mu.Unlock()
out := make([]*consoleInstance, 0, len(m.insts))
out = append(out, m.insts...)
return out, m.activeID
}
func (m *consoleManager) findInstance(id string) *consoleInstance {
m.mu.Lock()
defer m.mu.Unlock()
for _, inst := range m.insts {
if inst != nil && inst.ID == id {
return inst
}
}
return nil
}
func (m *consoleManager) setActive(id string) {
m.mu.Lock()
defer m.mu.Unlock()
m.activeID = id
for _, inst := range m.insts {
if inst != nil && inst.ID == id {
inst.LastUsed = time.Now()
break
}
}
}
func (m *consoleManager) addInstance(configKey string, cfg *assets.ClientConfig) *consoleInstance {
m.mu.Lock()
defer m.mu.Unlock()
m.nextID++
id := fmt.Sprintf("%d", m.nextID)
now := time.Now()
inst := &consoleInstance{
ID: id,
ConfigKey: configKey,
Config: cfg,
CreatedAt: now,
LastUsed: now,
}
m.insts = append(m.insts, inst)
m.activeID = id
return inst
}
func (m *consoleManager) configInstanceCountsLocked(insts []*consoleInstance) map[string]int {
// Caller provides insts snapshot; we just count.
counts := map[string]int{}
for _, inst := range insts {
if inst == nil {
continue
}
counts[inst.ConfigKey]++
}
return counts
}
type switchAction int
const (
actionNone switchAction = iota
actionSwitchExisting
actionNewConsole
)
type switchResult struct {
Action switchAction
ExistingID string
ConfigKey string
}
type switchTab int
const (
tabExisting switchTab = iota
tabNew
)
type switchTUIModel struct {
tab switchTab
existing *huh.Form
newConn *huh.Form
result switchResult
width int
height int
theme *huh.Theme
styles switchStyles
activeID string
counts map[string]int
initOnce bool
keyMap *huh.KeyMap
}
type switchStyles struct {
frame lipgloss.Style
header lipgloss.Style
tabActive lipgloss.Style
tabInactive lipgloss.Style
footer lipgloss.Style
footerDim lipgloss.Style
divider lipgloss.Style
}
func runSwitchTUI(instances []*consoleInstance, activeID string, configKeys []string, counts map[string]int) (switchResult, error) {
m := newSwitchTUIModel(instances, activeID, configKeys, counts)
prog := tea.NewProgram(m, tea.WithAltScreen())
finalModel, err := prog.Run()
if err != nil {
return switchResult{}, err
}
out, ok := finalModel.(switchTUIModel)
if !ok {
return switchResult{}, fmt.Errorf("unexpected TUI state")
}
return out.result, nil
}
func newSwitchTUIModel(instances []*consoleInstance, activeID string, configKeys []string, counts map[string]int) switchTUIModel {
keyMap := huh.NewDefaultKeyMap()
// Free up tab for switching between views; use enter to select/advance.
keyMap.Select.Next = key.NewBinding(key.WithKeys("enter"), key.WithHelp("enter", "select"))
keyMap.Select.Submit = key.NewBinding(key.WithKeys("enter"), key.WithHelp("enter", "submit"))
huhTheme := clienttheme.HuhTheme()
styles := switchStyles{
frame: lipgloss.NewStyle().
Border(lipgloss.RoundedBorder()).
BorderForeground(clienttheme.DefaultMod(300)).
Padding(0, 1),
header: lipgloss.NewStyle().MarginBottom(1),
tabActive: lipgloss.NewStyle().
Bold(true).
Foreground(clienttheme.DefaultMod(900)).
Background(clienttheme.Primary()).
Padding(0, 1),
tabInactive: lipgloss.NewStyle().
Foreground(clienttheme.DefaultMod(500)).
Background(clienttheme.DefaultMod(50)).
Padding(0, 1),
footer: lipgloss.NewStyle().MarginTop(1),
footerDim: lipgloss.NewStyle().Foreground(clienttheme.DefaultMod(400)),
divider: lipgloss.NewStyle().Foreground(clienttheme.DefaultMod(300)),
}
existingID := activeID
existingOptions := make([]huh.Option[string], 0, len(instances))
for _, inst := range instances {
if inst == nil {
continue
}
label := fmt.Sprintf("%s %s", inst.DisplayName(), inst.ConfigKey)
if inst.ID == activeID && activeID != "" {
label = fmt.Sprintf("* %s", label)
}
existingOptions = append(existingOptions, huh.NewOption(label, inst.ID))
}
if len(existingOptions) == 0 {
// Always provide at least one option so Huh doesn't error.
existingOptions = append(existingOptions, huh.NewOption("(no existing consoles)", ""))
}
existingSelect := huh.NewSelect[string]().
Title("Existing Consoles").
Description("Switch to an already-created console context. Use / to filter.").
Options(existingOptions...).
Key("existing").
Value(&existingID)
existingForm := huh.NewForm(huh.NewGroup(existingSelect)).
WithTheme(huhTheme).
WithKeyMap(keyMap).
WithShowHelp(false)
newKey := ""
configOptions := make([]huh.Option[string], 0, len(configKeys))
for _, k := range configKeys {
label := k
if n := counts[k]; n > 0 {
label = fmt.Sprintf("%s (existing: %d)", k, n)
}
configOptions = append(configOptions, huh.NewOption(label, k))
if newKey == "" {
newKey = k
}
}
newSelect := huh.NewSelect[string]().
Title("New Connection / Console").
Description("Create a new console context from a profile in ~/.sliver-client/configs. Use / to filter.").
Options(configOptions...).
Key("config").
Value(&newKey)
newForm := huh.NewForm(huh.NewGroup(newSelect)).
WithTheme(huhTheme).
WithKeyMap(keyMap).
WithShowHelp(false)
return switchTUIModel{
tab: tabExisting,
existing: existingForm,
newConn: newForm,
theme: huhTheme,
styles: styles,
activeID: activeID,
counts: counts,
keyMap: keyMap,
}
}
func (m switchTUIModel) Init() tea.Cmd {
// Initialize both forms so they're ready when switching tabs.
return tea.Batch(m.existing.Init(), m.newConn.Init())
}
func (m switchTUIModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.WindowSizeMsg:
m.width = msg.Width
m.height = msg.Height
// Forward to both forms so they size correctly.
modelA, cmdA := m.existing.Update(msg)
modelB, cmdB := m.newConn.Update(msg)
m.existing = modelA.(*huh.Form)
m.newConn = modelB.(*huh.Form)
return m, tea.Batch(cmdA, cmdB)
case tea.KeyMsg:
switch msg.String() {
case "tab":
if m.tab == tabExisting {
m.tab = tabNew
} else {
m.tab = tabExisting
}
return m, nil
case "shift+tab":
if m.tab == tabExisting {
m.tab = tabNew
} else {
m.tab = tabExisting
}
return m, nil
}
}
// Delegate to active tab's form.
switch m.tab {
case tabExisting:
model, cmd := m.existing.Update(msg)
m.existing = model.(*huh.Form)
if m.existing.State == huh.StateCompleted {
id := m.existing.GetString("existing")
if id != "" {
m.result = switchResult{Action: actionSwitchExisting, ExistingID: id}
}
return m, tea.Quit
}
if m.existing.State == huh.StateAborted {
m.result = switchResult{Action: actionNone}
return m, tea.Quit
}
return m, cmd
case tabNew:
model, cmd := m.newConn.Update(msg)
m.newConn = model.(*huh.Form)
if m.newConn.State == huh.StateCompleted {
key := m.newConn.GetString("config")
if key != "" {
m.result = switchResult{Action: actionNewConsole, ConfigKey: key}
}
return m, tea.Quit
}
if m.newConn.State == huh.StateAborted {
m.result = switchResult{Action: actionNone}
return m, tea.Quit
}
return m, cmd
}
return m, nil
}
func (m switchTUIModel) View() string {
header := m.renderHeader()
body := ""
switch m.tab {
case tabExisting:
body = m.existing.View()
case tabNew:
body = m.newConn.View()
}
footer := m.renderFooter()
content := lipgloss.JoinVertical(lipgloss.Left, header, body, footer)
return m.styles.frame.Render(content)
}
func (m switchTUIModel) renderHeader() string {
left := m.styles.tabInactive.Render("Existing")
right := m.styles.tabInactive.Render("New")
if m.tab == tabExisting {
left = m.styles.tabActive.Render("Existing")
} else {
right = m.styles.tabActive.Render("New")
}
line := lipgloss.JoinHorizontal(lipgloss.Left, left, m.styles.divider.Render(" "), right)
return m.styles.header.Render(line)
}
func (m switchTUIModel) renderFooter() string {
keys := "tab: switch view enter: select /: filter ctrl+c: cancel"
return m.styles.footerDim.Render(keys)
}
func operatorFromConfig(cfg *assets.ClientConfig) string {
if cfg == nil {
return ""
}
// Prefer the certificate CN, since config.Operator is mostly informational.
block, _ := pem.Decode([]byte(cfg.Certificate))
if block != nil {
cert, err := x509.ParseCertificate(block.Bytes)
if err == nil && cert.Subject.CommonName != "" {
return cert.Subject.CommonName
}
}
if strings.TrimSpace(cfg.Operator) != "" {
return strings.TrimSpace(cfg.Operator)
}
return ""
}
func shortCertFingerprint(cfg *assets.ClientConfig) string {
if cfg == nil {
return ""
}
digest := sha256.Sum256([]byte(cfg.Certificate))
return fmt.Sprintf("%x", digest[:8])
}
+13
View File
@@ -0,0 +1,13 @@
//go:build !client
package serverctx
import (
"github.com/bishopfox/sliver/client/console"
"github.com/spf13/cobra"
)
// Commands is a no-op when building without the `client` build tag (e.g. sliver-server).
func Commands(_ *console.SliverClient) []*cobra.Command {
return nil
}
+20 -20
View File
@@ -192,15 +192,15 @@ func PrintSessions(sessions map[string]*clientpb.Session, filter string, filterR
})
for _, session := range sessions {
color := console.Normal
style := console.StyleNormal
if con.ActiveTarget.GetSession() != nil && con.ActiveTarget.GetSession().ID == session.ID {
color = console.Green
style = console.StyleGreen
}
var SessionHealth string
if session.IsDead {
SessionHealth = console.Bold + console.Red + "[DEAD]" + console.Normal
SessionHealth = console.StyleBoldRed.Render("[DEAD]")
} else {
SessionHealth = console.Bold + console.Green + "[ALIVE]" + console.Normal
SessionHealth = console.StyleBoldGreen.Render("[ALIVE]")
}
burned := ""
if session.Burned {
@@ -211,33 +211,33 @@ func PrintSessions(sessions map[string]*clientpb.Session, filter string, filterR
var rowEntries []string
if wideTermWidth {
rowEntries = []string{
fmt.Sprintf(color+"%s"+console.Normal, ShortSessionID(session.ID)),
fmt.Sprintf(color+"%s"+console.Normal, session.Name),
fmt.Sprintf(color+"%s"+console.Normal, session.Transport),
fmt.Sprintf(color+"%s"+console.Normal, session.RemoteAddress),
fmt.Sprintf(color+"%s"+console.Normal, session.Hostname),
fmt.Sprintf(color+"%s"+console.Normal, username),
fmt.Sprintf(color+"%s (%d)"+console.Normal, session.Filename, session.PID),
style.Render(ShortSessionID(session.ID)),
style.Render(session.Name),
style.Render(session.Transport),
style.Render(session.RemoteAddress),
style.Render(session.Hostname),
style.Render(username),
style.Render(fmt.Sprintf("%s (%d)", session.Filename, session.PID)),
}
if windowsSessionInList {
rowEntries = append(rowEntries, fmt.Sprintf(color+"%s"+console.Normal, session.Integrity))
rowEntries = append(rowEntries, style.Render(session.Integrity))
}
rowEntries = append(rowEntries, []string{
fmt.Sprintf(color+"%s/%s"+console.Normal, session.OS, session.Arch),
fmt.Sprintf(color+"%s"+console.Normal, session.Locale),
style.Render(fmt.Sprintf("%s/%s", session.OS, session.Arch)),
style.Render(session.Locale),
con.FormatDateDelta(time.Unix(session.LastCheckin, 0), wideTermWidth, false),
burned + SessionHealth,
}...)
} else {
rowEntries = []string{
fmt.Sprintf(color+"%s"+console.Normal, ShortSessionID(session.ID)),
fmt.Sprintf(color+"%s"+console.Normal, session.Transport),
fmt.Sprintf(color+"%s"+console.Normal, session.RemoteAddress),
fmt.Sprintf(color+"%s"+console.Normal, session.Hostname),
fmt.Sprintf(color+"%s"+console.Normal, username),
fmt.Sprintf(color+"%s/%s"+console.Normal, session.OS, session.Arch),
style.Render(ShortSessionID(session.ID)),
style.Render(session.Transport),
style.Render(session.RemoteAddress),
style.Render(session.Hostname),
style.Render(username),
style.Render(fmt.Sprintf("%s/%s", session.OS, session.Arch)),
burned + SessionHealth,
}
}
+13
View File
@@ -20,6 +20,7 @@ package settings
import (
"errors"
"path/filepath"
"strconv"
"strings"
@@ -58,12 +59,22 @@ func SettingsCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
con.PrintErrorf("Failed to apply settings form values: %s\n", err)
return
}
if err := assets.SaveSettings(con.Settings); err != nil {
con.PrintErrorf("Failed to save settings: %s\n", err)
return
}
if assets.NormalizePromptStyle(result.PromptStyle) == assets.PromptStyleCustom {
settingsPath := filepath.Join(assets.GetRootAppDir(), "tui-settings.yaml")
con.PrintInfof("Prompt style is %q. Edit %s to set %q.\n", assets.PromptStyleCustom, settingsPath, "prompt_template")
con.Println("")
}
}
tw := table.NewWriter()
tw.SetStyle(GetTableStyle(con))
tw.AppendHeader(table.Row{"Name", "Value", "Description"})
tw.AppendRow(table.Row{"Tables", con.Settings.TableStyle, "Set the stylization of tables"})
tw.AppendRow(table.Row{"Prompt", con.Settings.PromptStyle, "Set the console prompt prefix style"})
tw.AppendRow(table.Row{"Auto Adult", con.Settings.AutoAdult, "Automatically accept OPSEC warnings"})
tw.AppendRow(table.Row{"Auto Beacon Results", con.Settings.BeaconAutoResults, "Automatically display beacon results when tasks complete"})
tw.AppendRow(table.Row{"Small Term Width", con.Settings.SmallTermWidth, "Omit some table columns when terminal width is less than this value"})
@@ -221,6 +232,7 @@ func applySettingsForm(settings *assets.ClientSettings, result *forms.SettingsFo
if width < 1 {
return errors.New("small terminal width must be 1 or greater")
}
promptStyle := assets.NormalizePromptStyle(result.PromptStyle)
settings.TableStyle = result.TableStyle
settings.AutoAdult = result.AutoAdult
@@ -230,5 +242,6 @@ func applySettingsForm(settings *assets.ClientSettings, result *forms.SettingsFo
settings.VimMode = result.VimMode
settings.UserConnect = result.UserConnect
settings.ConsoleLogs = result.ConsoleLogs
settings.PromptStyle = promptStyle
return nil
}
+2 -2
View File
@@ -218,12 +218,12 @@ func PaginateTable(tw table.Writer, skipPages int, overflow bool, interactive bo
}
con.Println()
} else {
con.Printf(console.Bold+"Page [%d/%d]\n", pageNumber+1, len(pages))
con.Printf("%s\n", console.StyleBold.Render(fmt.Sprintf("Page [%d/%d]", pageNumber+1, len(pages))))
}
}
} else {
if 1 < len(pages) {
con.Printf(console.Bold+"Page [%d/%d]\n", pageNumber+1, len(pages))
con.Printf("%s\n", console.StyleBold.Render(fmt.Sprintf("Page [%d/%d]", pageNumber+1, len(pages))))
}
break
}
+5 -2
View File
@@ -6,7 +6,10 @@ Implements the 'shell' command group for the Sliver client console. Handlers map
## Go Files
- `commands.go` – Exposes the interactive shell command and binds its options.
- `commands.go` – Exposes the shell command group (`shell`, `shell ls`, `shell attach`, `shell kill`) and binds options.
- `completers.go` – Shell-specific command completion helpers.
- `filter-reader_generic.go` – Provides output filtering for shell streams on POSIX targets.
- `filter-reader_windows.go` – Implements CRLF-aware filtering and decoding for Windows shell sessions.
- `shell.go` – Launches interactive command shells over RPC and manages IO loops.
- `ioloop.go` – Handles attached shell stdin forwarding, escape handling, and fast local detach/exit behavior.
- `manager.go` – Tracks managed shell tunnels so detached shells can be listed and re-attached.
- `shell.go` – Launches/attaches shell tunnels and coordinates managed shell lifecycle.
+42
View File
@@ -5,6 +5,7 @@ import (
"github.com/bishopfox/sliver/client/command/help"
"github.com/bishopfox/sliver/client/console"
consts "github.com/bishopfox/sliver/client/constants"
"github.com/rsteube/carapace"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
)
@@ -28,5 +29,46 @@ func Commands(con *console.SliverClient) []*cobra.Command {
f.Int64P("timeout", "t", flags.DefaultTimeout, "grpc timeout in seconds")
})
shellLsCmd := &cobra.Command{
Use: consts.LsStr,
Short: "List managed local shell tunnels",
Long: help.GetHelpFor([]string{consts.ShellStr}),
Run: func(cmd *cobra.Command, args []string) {
ShellLsCmd(cmd, con, args)
},
}
shellCmd.AddCommand(shellLsCmd)
shellAttachCmd := &cobra.Command{
Use: "attach <id>",
Short: "Attach to a managed local shell tunnel",
Long: help.GetHelpFor([]string{consts.ShellStr}),
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
ShellAttachCmd(cmd, con, args)
},
}
flags.Bind("", false, shellAttachCmd, func(f *pflag.FlagSet) {
f.Int64P("timeout", "t", flags.DefaultTimeout, "grpc timeout in seconds")
})
shellCmd.AddCommand(shellAttachCmd)
carapace.Gen(shellAttachCmd).PositionalCompletion(ShellIDCompleter(con).Usage("managed shell ID"))
shellKillCmd := &cobra.Command{
Use: consts.KillStr + " <id>",
Short: "Kill a managed shell and clean up remote process state",
Long: help.GetHelpFor([]string{consts.ShellStr}),
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
ShellKillCmd(cmd, con, args)
},
}
flags.Bind("", false, shellKillCmd, func(f *pflag.FlagSet) {
f.BoolP("force", "F", false, "force process cleanup via terminate rpc")
f.Int64P("timeout", "t", flags.DefaultTimeout, "grpc timeout in seconds")
})
shellCmd.AddCommand(shellKillCmd)
carapace.Gen(shellKillCmd).PositionalCompletion(ShellIDCompleter(con).Usage("managed shell ID"))
return []*cobra.Command{shellCmd}
}
+30
View File
@@ -0,0 +1,30 @@
package shell
import (
"fmt"
"strconv"
"github.com/bishopfox/sliver/client/console"
"github.com/rsteube/carapace"
)
// ShellIDCompleter completes IDs of managed local shells.
func ShellIDCompleter(_ *console.SliverClient) carapace.Action {
callback := func(_ carapace.Context) carapace.Action {
results := make([]string, 0)
managed := shells.List()
if len(managed) == 0 {
return carapace.ActionMessage("no managed shells")
}
for _, sh := range managed {
results = append(results, strconv.Itoa(sh.ID))
results = append(results, fmt.Sprintf("%s, pid=%d, state=%s", sessionLabel(sh.SessionID, sh.SessionName), sh.Pid, sh.State()))
}
return carapace.ActionValuesDescribed(results...).Tag("managed shells")
}
return carapace.ActionCallback(callback)
}
+148
View File
@@ -0,0 +1,148 @@
package shell
import (
"bufio"
"bytes"
"io"
"log"
"os"
"strings"
"sync"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/core"
)
// runAttachedIO forwards local stdin to a shell tunnel while attached to the shell.
// It returns:
// - detached=true when user hit escape (Ctrl-])
// - closeRequested=true when user requested shell close (EOF/exit/logout)
func runAttachedIO(tunnel *core.TunnelIO, con *console.SliverClient) (detached bool, closeRequested bool) {
// We can't pipe stdin directly into the tunnel: if the gRPC tunnel send path blocks
// (network hang, server gone, etc.) tunnel.Write can block forever and "lock" the
// client in shell mode. Instead, buffer stdin locally and look for a local escape key.
const (
shellEscapeByte = byte(0x1d) // Ctrl-]
stdinBufSize = 32 * 1024
stdinQueueDepth = 128
)
stdinQueue := make(chan []byte, stdinQueueDepth)
stopWriter := make(chan struct{})
var writerWG sync.WaitGroup
writerWG.Add(1)
go func() {
defer writerWG.Done()
defer func() {
// Tunnel teardown can race with writes (server-side close, disconnect, etc.).
// Don't let a send-on-closed-channel panic bring down the whole client.
if r := recover(); r != nil {
log.Printf("Shell tunnel writer stopped: %v", r)
}
}()
for {
select {
case <-stopWriter:
return
case data, ok := <-stdinQueue:
if !ok {
return
}
if len(data) == 0 {
continue
}
select {
case <-stopWriter:
return
case tunnel.Send <- data:
}
}
}
}()
defer func() {
close(stopWriter)
close(stdinQueue)
writerWG.Wait()
}()
stdin := newFilterReader(os.Stdin)
buf := make([]byte, stdinBufSize)
lineBuf := make([]byte, 0, 128)
inEscSeq := false
log.Printf("Reading from stdin (escape=Ctrl-]) ...")
for {
n, err := stdin.Read(buf)
if n > 0 {
data := buf[:n]
if i := bytes.IndexByte(data, shellEscapeByte); i >= 0 {
if i > 0 {
// Best-effort: don't block stdin if the tunnel is wedged.
dataCopy := append([]byte(nil), data[:i]...)
select {
case stdinQueue <- dataCopy:
default:
}
}
detached = true
break
}
exitRequested := false
for _, b := range data {
if inEscSeq {
if b >= 0x40 && b <= 0x7e {
inEscSeq = false
}
continue
}
switch b {
case '\r', '\n':
line := strings.TrimSpace(string(lineBuf))
if line == "exit" || line == "logout" {
exitRequested = true
}
lineBuf = lineBuf[:0]
case 0x1b:
inEscSeq = true
case 0x08, 0x7f:
if len(lineBuf) > 0 {
lineBuf = lineBuf[:len(lineBuf)-1]
}
default:
// Keep printable bytes only; ignore control sequences (arrows, etc.).
if b >= 0x20 && b <= 0x7e {
lineBuf = append(lineBuf, b)
}
}
}
dataCopy := append([]byte(nil), data...)
select {
case stdinQueue <- dataCopy:
default:
// Drop input if the tunnel send path is blocked; still allow escape.
}
if exitRequested {
closeRequested = true
break
}
}
if err != nil {
if err != io.EOF {
con.PrintErrorf("Error reading from stdin: %s\n", err)
}
closeRequested = true
break
}
}
log.Printf("Exit interactive")
bufio.NewWriter(os.Stdout).Flush()
return detached, closeRequested
}
+150
View File
@@ -0,0 +1,150 @@
package shell
import (
"io"
"log"
"sort"
"sync"
"time"
"github.com/bishopfox/sliver/client/core"
)
type shellManager struct {
mu sync.RWMutex
nextID int
shells map[int]*managedShell
}
func newShellManager() *shellManager {
return &shellManager{
nextID: 1,
shells: map[int]*managedShell{},
}
}
var shells = newShellManager()
type shellState string
const (
shellStateAttached shellState = "attached"
shellStateDetached shellState = "detached"
shellStateClosing shellState = "closing"
)
type managedShell struct {
ID int
SessionID string
SessionName string
Path string
Pid uint32
TunnelID uint64
EnablePTY bool
CreatedAt time.Time
mu sync.RWMutex
state shellState
tunnel *core.TunnelIO
output *swapWriter
readerWG sync.WaitGroup
}
func (s *managedShell) State() shellState {
s.mu.RLock()
defer s.mu.RUnlock()
return s.state
}
func (s *managedShell) setState(state shellState) {
s.mu.Lock()
defer s.mu.Unlock()
s.state = state
}
func (s *managedShell) Tunnel() *core.TunnelIO {
s.mu.RLock()
defer s.mu.RUnlock()
return s.tunnel
}
func (s *managedShell) SetOutput(w io.Writer) {
s.output.Set(w)
}
func (s *managedShell) startReader(remove func()) {
s.readerWG.Add(1)
go func() {
defer s.readerWG.Done()
_, err := io.Copy(s.output, s.tunnel)
log.Printf("Shell tunnel reader (id=%d tunnel=%d) exited: %v", s.ID, s.TunnelID, err)
remove()
}()
}
// swapWriter is an io.Writer whose underlying destination can be swapped at runtime.
// This is used so detached shells can keep draining output without clobbering the REPL.
type swapWriter struct {
mu sync.RWMutex
w io.Writer
}
func newSwapWriter(w io.Writer) *swapWriter {
return &swapWriter{w: w}
}
func (s *swapWriter) Set(w io.Writer) {
s.mu.Lock()
s.w = w
s.mu.Unlock()
}
func (s *swapWriter) Write(p []byte) (int, error) {
s.mu.RLock()
w := s.w
s.mu.RUnlock()
if w == nil {
return len(p), nil
}
return w.Write(p)
}
func (m *shellManager) Add(sh *managedShell) int {
m.mu.Lock()
defer m.mu.Unlock()
id := m.nextID
m.nextID++
sh.ID = id
m.shells[id] = sh
return id
}
func (m *shellManager) Remove(id int) {
m.mu.Lock()
defer m.mu.Unlock()
delete(m.shells, id)
}
func (m *shellManager) Get(id int) (*managedShell, bool) {
m.mu.RLock()
defer m.mu.RUnlock()
sh, ok := m.shells[id]
return sh, ok
}
func (m *shellManager) List() []*managedShell {
m.mu.RLock()
defer m.mu.RUnlock()
results := make([]*managedShell, 0, len(m.shells))
for _, sh := range m.shells {
results = append(results, sh)
}
sort.Slice(results, func(i, j int) bool {
return results[i].ID < results[j].ID
})
return results
}
+351 -90
View File
@@ -19,24 +19,34 @@ package shell
*/
import (
"bufio"
"context"
"io"
"log"
"os"
"strconv"
"strings"
"time"
"github.com/bishopfox/sliver/client/command/settings"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/core"
"github.com/bishopfox/sliver/protobuf/sliverpb"
"github.com/jedib0t/go-pretty/v6/table"
"github.com/spf13/cobra"
"golang.org/x/term"
)
const (
windows = "windows"
darwin = "darwin"
linux = "linux"
darwin = "darwin"
linux = "linux"
)
type shellResult int
const (
shellExited shellResult = iota
shellDetached
shellAttachFailed
)
// ShellCmd - Start an interactive shell on the remote system.
@@ -55,113 +65,364 @@ func ShellCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
if con.ActiveTarget.GetSession().OS != linux && con.ActiveTarget.GetSession().OS != darwin {
noPty = true // Sliver's PTYs are only supported on linux/darwin
}
runInteractive(cmd, shellPath, noPty, con)
con.Println("Shell exited")
result := runInteractive(cmd, shellPath, noPty, con, nil)
if result == shellDetached {
con.Println("Shell detached")
} else if result == shellExited {
con.Println("Shell exited")
}
}
func runInteractive(cmd *cobra.Command, shellPath string, noPty bool, con *console.SliverClient) {
// ShellLsCmd lists local shell tunnels currently managed by the client.
func ShellLsCmd(_ *cobra.Command, con *console.SliverClient, _ []string) {
managed := shells.List()
if len(managed) == 0 {
con.PrintInfof("No managed shells\n")
return
}
tw := table.NewWriter()
tw.SetStyle(settings.GetTableStyle(con))
tw.AppendHeader(table.Row{
"ID",
"State",
"Session",
"PID",
"PTY",
"Tunnel ID",
"Path",
"Age",
})
now := time.Now()
for _, sh := range managed {
age := now.Sub(sh.CreatedAt).Round(time.Second).String()
if sh.CreatedAt.IsZero() {
age = "-"
}
pid := "-"
if sh.Pid > 0 {
pid = strconv.FormatUint(uint64(sh.Pid), 10)
}
tw.AppendRow(table.Row{
sh.ID,
sh.State(),
sessionLabel(sh.SessionID, sh.SessionName),
pid,
sh.EnablePTY,
sh.TunnelID,
sh.Path,
age,
})
}
con.Printf("%s\n", tw.Render())
}
// ShellAttachCmd re-attaches local stdin/stdout to a managed shell tunnel.
func ShellAttachCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
if len(args) < 1 {
con.PrintErrorf("Usage: shell attach <id>\n")
return
}
shellID, err := strconv.Atoi(args[0])
if err != nil || shellID <= 0 {
con.PrintErrorf("Invalid shell id: %s\n", args[0])
return
}
sh, ok := shells.Get(shellID)
if !ok {
con.PrintErrorf("Unknown shell id: %d\n", shellID)
return
}
if sh.State() == shellStateAttached {
con.PrintErrorf("Shell %d is already attached\n", shellID)
return
}
if sh.State() == shellStateClosing {
con.PrintErrorf("Shell %d is closing\n", shellID)
return
}
tunnel := sh.Tunnel()
if tunnel == nil || !tunnel.IsOpen() {
con.PrintErrorf("Shell %d is no longer active\n", shellID)
shells.Remove(shellID)
return
}
session := con.ActiveTarget.GetSession()
if session == nil || session.ID != sh.SessionID {
con.PrintErrorf("Shell %d belongs to %s; switch targets before attaching\n", shellID, sessionLabel(sh.SessionID, sh.SessionName))
return
}
result := runInteractive(cmd, sh.Path, !sh.EnablePTY, con, sh)
if result == shellDetached {
con.Println("Shell detached")
} else if result == shellExited {
con.Println("Shell exited")
}
}
// ShellKillCmd kills a managed shell from the REPL and cleans up remote state.
func ShellKillCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
if len(args) < 1 {
con.PrintErrorf("Usage: shell kill <id>\n")
return
}
shellID, err := strconv.Atoi(args[0])
if err != nil || shellID <= 0 {
con.PrintErrorf("Invalid shell id: %s\n", args[0])
return
}
force, _ := cmd.Flags().GetBool("force")
sh, ok := shells.Get(shellID)
if !ok {
con.PrintErrorf("Unknown shell id: %d\n", shellID)
return
}
session := con.ActiveTarget.GetSession()
if session == nil || session.ID != sh.SessionID {
con.PrintErrorf("Shell %d belongs to %s; switch targets before killing\n", shellID, sessionLabel(sh.SessionID, sh.SessionName))
return
}
sh.setState(shellStateClosing)
sh.SetOutput(io.Discard)
shells.Remove(sh.ID)
// Best-effort graceful request before closing the tunnel.
requestRemoteShellExit(sh.Tunnel())
if err := closeShellTunnel(con, sh.TunnelID, sh.SessionID); err != nil {
con.PrintWarnf("Error closing shell tunnel %d: %s\n", sh.TunnelID, err)
}
if force && sh.Pid > 1 {
terminate, err := con.Rpc.Terminate(context.Background(), &sliverpb.TerminateReq{
Request: con.ActiveTarget.Request(cmd),
Pid: int32(sh.Pid),
Force: force,
})
if err != nil {
con.PrintWarnf("Error terminating shell process %d: %s\n", sh.Pid, err)
} else if terminate.Response != nil && terminate.Response.GetErr() != "" {
errMsg := terminate.Response.GetErr()
// If shell already exited, that is an acceptable cleanup outcome.
if strings.Contains(strings.ToLower(errMsg), "no such process") ||
strings.Contains(strings.ToLower(errMsg), "process not found") {
con.PrintInfof("Shell process %d already exited\n", sh.Pid)
} else {
con.PrintWarnf("Terminate response for shell process %d: %s\n", sh.Pid, errMsg)
}
} else {
con.PrintInfof("Shell process %d terminated\n", sh.Pid)
}
}
if force {
con.PrintInfof("Shell %d killed (force)\n", sh.ID)
} else {
con.PrintInfof("Shell %d killed\n", sh.ID)
}
}
func runInteractive(cmd *cobra.Command, shellPath string, noPty bool, con *console.SliverClient, existing *managedShell) shellResult {
con.Println()
con.PrintInfof("Wait approximately 10 seconds after exit, and press <enter> to continue\n")
con.PrintInfof("Opening shell tunnel (EOF to exit) ...\n\n")
con.PrintInfof("Shell management: `shell ls`, `shell attach <id>`\n")
con.PrintInfof("Escape: press Ctrl-] to return to the Sliver client\n")
session := con.ActiveTarget.GetSession()
if session == nil {
return
return shellAttachFailed
}
// Create an RPC tunnel, then start it before binding the shell to the newly created tunnel
ctxTunnel, cancelTunnel := context.WithCancel(context.Background())
rpcTunnel, err := con.Rpc.CreateTunnel(ctxTunnel, &sliverpb.Tunnel{
SessionID: session.ID,
})
defer cancelTunnel()
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
log.Printf("Created new tunnel with id: %d, binding to shell ...", rpcTunnel.TunnelID)
// Start() takes an RPC tunnel and creates a local Reader/Writer tunnel object
tunnel := core.GetTunnels().Start(rpcTunnel.TunnelID, rpcTunnel.SessionID)
var rows uint32
var cols uint32
if !noPty {
colsInt, rowsInt, err := term.GetSize(int(os.Stdout.Fd()))
if err != nil || rowsInt <= 0 || colsInt <= 0 {
colsInt, rowsInt, err = term.GetSize(int(os.Stdin.Fd()))
}
if err == nil && rowsInt > 0 && colsInt > 0 {
rows = uint32(rowsInt)
cols = uint32(colsInt)
}
if existing == nil {
con.PrintInfof("Opening shell tunnel ...\n\n")
} else {
con.PrintInfof("Re-attaching to shell %d ...\n\n", existing.ID)
}
shell, err := con.Rpc.Shell(context.Background(), &sliverpb.ShellReq{
Request: con.ActiveTarget.Request(cmd),
Path: shellPath,
EnablePTY: !noPty,
Rows: rows,
Cols: cols,
TunnelID: tunnel.ID,
})
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
//
if shell.Response != nil && shell.Response.Err != "" {
con.PrintErrorf("Error: %s\n", shell.Response.Err)
_, err = con.Rpc.CloseTunnel(context.Background(), &sliverpb.Tunnel{
TunnelID: tunnel.ID,
var (
tunnel *core.TunnelIO
managed *managedShell
enablePTY bool
oldState *term.State
stateSaved bool
stopPtySize = func() {}
err error
)
if existing == nil {
// Create an RPC tunnel, then start it before binding the shell to the newly created tunnel.
ctxTunnel, cancelTunnel := context.WithCancel(context.Background())
defer cancelTunnel()
rpcTunnel, err := con.Rpc.CreateTunnel(ctxTunnel, &sliverpb.Tunnel{
SessionID: session.ID,
})
if err != nil {
con.PrintErrorf("RPC Error: %s\n", err)
con.PrintErrorf("%s\n", err)
return shellAttachFailed
}
return
}
defer tunnel.Close()
log.Printf("Bound remote shell pid %d to tunnel %d", shell.Pid, shell.TunnelID)
con.PrintInfof("Started remote shell with pid %d\n\n", shell.Pid)
log.Printf("Created new tunnel with id: %d, binding to shell ...", rpcTunnel.TunnelID)
var oldState *term.State
if !noPty {
// Start() takes an RPC tunnel and creates a local Reader/Writer tunnel object.
tunnel = core.GetTunnels().Start(rpcTunnel.TunnelID, rpcTunnel.SessionID)
var rows uint32
var cols uint32
if !noPty {
colsInt, rowsInt, err := term.GetSize(int(os.Stdout.Fd()))
if err != nil || rowsInt <= 0 || colsInt <= 0 {
colsInt, rowsInt, err = term.GetSize(int(os.Stdin.Fd()))
}
if err == nil && rowsInt > 0 && colsInt > 0 {
rows = uint32(rowsInt)
cols = uint32(colsInt)
}
}
shell, err := con.Rpc.Shell(context.Background(), &sliverpb.ShellReq{
Request: con.ActiveTarget.Request(cmd),
Path: shellPath,
EnablePTY: !noPty,
Rows: rows,
Cols: cols,
TunnelID: tunnel.ID,
})
if err != nil {
con.PrintErrorf("%s\n", err)
core.GetTunnels().Close(tunnel.ID)
return shellAttachFailed
}
if shell.Response != nil && shell.Response.Err != "" {
con.PrintErrorf("Error: %s\n", shell.Response.Err)
go backgroundCloseShell(con, tunnel.ID, session.ID)
return shellAttachFailed
}
path := shellPath
if path == "" {
path = shell.Path
}
enablePTY = !noPty
managed = &managedShell{
SessionID: session.ID,
SessionName: session.Name,
Path: path,
Pid: shell.Pid,
TunnelID: tunnel.ID,
EnablePTY: enablePTY,
CreatedAt: time.Now(),
state: shellStateAttached,
tunnel: tunnel,
output: newSwapWriter(os.Stdout),
}
managed.ID = shells.Add(managed)
managed.startReader(func() {
shells.Remove(managed.ID)
})
log.Printf("Bound remote shell pid %d to tunnel %d", shell.Pid, shell.TunnelID)
con.PrintInfof("Started remote shell [%d] with pid %d\n\n", managed.ID, shell.Pid)
} else {
managed = existing
tunnel = managed.Tunnel()
enablePTY = managed.EnablePTY
managed.setState(shellStateAttached)
managed.SetOutput(os.Stdout)
con.PrintInfof("Attached shell [%d] (pid %d)\n\n", managed.ID, managed.Pid)
}
if enablePTY {
oldState, err = term.MakeRaw(0)
log.Printf("Saving terminal state: %v", oldState)
if err != nil {
con.PrintErrorf("Failed to save terminal state")
return
con.PrintErrorf("Failed to save terminal state\n")
managed.SetOutput(io.Discard)
managed.setState(shellStateDetached)
return shellAttachFailed
}
stateSaved = true
stopPtySize = startPtyResizeWatcher(con, cmd, tunnel.ID)
}
defer stopPtySize()
if stateSaved {
defer func() {
log.Printf("Restoring terminal state ...")
term.Restore(0, oldState)
}()
}
stopPtyResize := func() {}
if !noPty {
stopPtyResize = startPtyResizeWatcher(con, cmd, tunnel.ID)
}
defer stopPtyResize()
log.Printf("Starting stdin/stdout shell ...")
go func() {
n, err := io.Copy(os.Stdout, tunnel)
log.Printf("Wrote %d bytes to stdout", n)
if err != nil {
con.PrintErrorf("Error writing to stdout: %s", err)
return
}
}()
log.Printf("Reading from stdin ...")
n, err := io.Copy(tunnel, newFilterReader(os.Stdin))
log.Printf("Read %d bytes from stdin", n)
if err != nil && err != io.EOF {
con.PrintErrorf("Error reading from stdin: %s\n", err)
detached, _ := runAttachedIO(tunnel, con)
if detached {
managed.SetOutput(io.Discard)
managed.setState(shellStateDetached)
return shellDetached
}
if !noPty {
log.Printf("Restoring terminal state ...")
term.Restore(0, oldState)
}
managed.SetOutput(io.Discard)
managed.setState(shellStateClosing)
shells.Remove(managed.ID)
log.Printf("Exit interactive")
bufio.NewWriter(os.Stdout).Flush()
// "exit" should return immediately; tunnel close/cleanup is best-effort in the background.
go backgroundCloseShell(con, tunnel.ID, session.ID)
return shellExited
}
func backgroundCloseShell(con *console.SliverClient, tunnelID uint64, sessionID string) {
err := closeShellTunnel(con, tunnelID, sessionID)
if err != nil {
log.Printf("Background close tunnel %d failed: %v", tunnelID, err)
}
}
func closeShellTunnel(con *console.SliverClient, tunnelID uint64, sessionID string) error {
core.GetTunnels().Close(tunnelID)
if con == nil || con.Rpc == nil {
return nil
}
_, err := con.Rpc.CloseTunnel(context.Background(), &sliverpb.Tunnel{
TunnelID: tunnelID,
SessionID: sessionID,
})
return err
}
func requestRemoteShellExit(tunnel *core.TunnelIO) {
if tunnel == nil || !tunnel.IsOpen() {
return
}
for _, payload := range [][]byte{[]byte("exit\n"), []byte("logout\n")} {
func(data []byte) {
defer func() {
if r := recover(); r != nil {
log.Printf("Shell exit write skipped: %v", r)
}
}()
select {
case tunnel.Send <- append([]byte(nil), data...):
default:
}
}(payload)
}
}
func sessionLabel(sessionID, sessionName string) string {
if sessionName == "" {
return sessionID
}
return sessionName + " (" + sessionID + ")"
}
+2 -2
View File
@@ -190,7 +190,7 @@ func SelectMultipleBeaconsAndSessions(con *console.SliverClient) ([]*clientpb.Se
session.Username,
fmt.Sprintf("%s/%s", session.OS, session.Arch),
)
fmt.Fprintf(table, option+"\n")
fmt.Fprintln(table, option)
o := strings.ReplaceAll(option, "\t", "")
sessionOptionMap[o] = session
}
@@ -206,7 +206,7 @@ func SelectMultipleBeaconsAndSessions(con *console.SliverClient) ([]*clientpb.Se
beacon.Username,
fmt.Sprintf("%s/%s", beacon.OS, beacon.Arch),
)
fmt.Fprintf(table, option+"\n")
fmt.Fprintln(table, option)
o := strings.ReplaceAll(option, "\t", "")
beaconOptionMap[o] = beacon
}
+1 -1
View File
@@ -126,7 +126,7 @@ func filterTasksByTaskType(taskType string, tasks []*clientpb.BeaconTask) []*cli
func PrintTask(task *clientpb.BeaconTask, con *console.SliverClient) {
tw := table.NewWriter()
tw.SetStyle(settings.GetTableWithBordersStyle(con))
tw.AppendRow(table.Row{console.Bold + "Beacon Task" + console.Normal, task.ID})
tw.AppendRow(table.Row{console.StyleBold.Render("Beacon Task"), task.ID})
tw.AppendSeparator()
tw.AppendRow(table.Row{"State", emojiState(task.State) + " " + prettyState(strings.Title(task.State))})
tw.AppendRow(table.Row{"Description", task.Description})
+4 -4
View File
@@ -93,13 +93,13 @@ func PrintBeaconTasks(tasks []*clientpb.BeaconTask, cmd *cobra.Command, con *con
func prettyState(state string) string {
switch strings.ToLower(state) {
case "pending":
return console.Bold + state + console.Normal
return console.StyleBold.Render(state)
case "sent":
return console.Bold + console.Orange + state + console.Normal
return console.StyleBoldOrange.Render(state)
case "completed":
return console.Bold + console.Green + state + console.Normal
return console.StyleBoldGreen.Render(state)
case "canceled":
return console.Bold + console.Gray + state + console.Normal
return console.StyleBoldGray.Render(state)
default:
return state
}
+1 -1
View File
@@ -278,7 +278,7 @@ func WasmLsCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
cacheLine = " (cached)"
} else {
wasmRegistrationCache[idOf(con)] = append(wasmRegistrationCache[idOf(con)], extName)
cacheLine = console.Bold + console.Green + " +++" + console.Normal
cacheLine = console.StyleBoldGreen.Render(" +++")
}
con.PrintInfof(" %s%s\n", extName, cacheLine)
}
+160
View File
@@ -0,0 +1,160 @@
package console
import (
"context"
"errors"
"fmt"
"log"
"os"
"sync"
"time"
"github.com/bishopfox/sliver/client/assets"
"github.com/bishopfox/sliver/client/core"
"github.com/bishopfox/sliver/protobuf/rpcpb"
"google.golang.org/grpc"
"google.golang.org/grpc/connectivity"
)
// ConnectionDetails provides (optional) metadata about the active connection.
// In the sliver-client binary this is typically sourced from ~/.sliver-client/configs/*.
type ConnectionDetails struct {
ConfigKey string
Config *assets.ClientConfig
}
// CurrentConnection returns the current connection metadata, if any.
func (con *SliverClient) CurrentConnection() (*ConnectionDetails, connectivity.State, bool) {
con.connMu.Lock()
defer con.connMu.Unlock()
if con.grpcConn == nil {
return con.connDetails, connectivity.Idle, false
}
return con.connDetails, con.grpcConn.GetState(), true
}
// SetConnection swaps the active RPC connection, restarting streams (events/tunnels/logs).
// It is safe to call multiple times (e.g., on server switch).
func (con *SliverClient) SetConnection(rpc rpcpb.SliverRPCClient, grpcConn *grpc.ClientConn, details *ConnectionDetails) error {
con.connMu.Lock()
defer con.connMu.Unlock()
con.detachConnectionLocked()
con.Rpc = rpc
con.grpcConn = grpcConn
con.connDetails = details
if con.Rpc == nil || con.grpcConn == nil {
return nil
}
// Clear per-server state that should not be carried across connections.
con.EventListeners = &sync.Map{}
con.BeaconTaskCallbacksMutex.Lock()
con.BeaconTaskCallbacks = map[string]BeaconTaskCallback{}
con.BeaconTaskCallbacksMutex.Unlock()
con.ActiveTarget.Set(nil, nil)
ctx, cancel := context.WithCancel(context.Background())
con.connCancel = cancel
wg := &sync.WaitGroup{}
con.connWg = wg
wg.Add(1)
go func(wg *sync.WaitGroup) {
defer wg.Done()
con.startEventLoop(ctx)
}(wg)
wg.Add(1)
go func(wg *sync.WaitGroup) {
defer wg.Done()
if err := core.TunnelLoop(ctx, con.Rpc); err != nil && !errors.Is(err, context.Canceled) {
log.Printf("TunnelLoop error: %v", err)
}
}(wg)
wg.Add(1)
go func(wg *sync.WaitGroup, conn *grpc.ClientConn) {
defer wg.Done()
con.monitorConnectionLost(ctx, conn)
}(wg, con.grpcConn)
con.refreshRemoteLogStreamsLocked()
return nil
}
// CloseConnection stops background loops and closes the current gRPC connection.
func (con *SliverClient) CloseConnection() error {
con.connMu.Lock()
defer con.connMu.Unlock()
con.detachConnectionLocked()
return nil
}
func (con *SliverClient) detachConnectionLocked() {
// Stop sending remote logs first so background io.Copy loops can't break.
con.setRemoteLogStreamsLocked(nil, nil)
cancel := con.connCancel
con.connCancel = nil
wg := con.connWg
con.connWg = &sync.WaitGroup{}
if cancel != nil {
cancel()
}
if wg != nil {
done := make(chan struct{})
go func() {
wg.Wait()
close(done)
}()
select {
case <-done:
case <-time.After(2 * time.Second):
// Best-effort; we don't want to deadlock a switch/exit.
}
}
if con.grpcConn != nil {
_ = con.grpcConn.Close()
con.grpcConn = nil
}
con.Rpc = nil
con.connDetails = nil
// Tear down any singleton network tooling that was tied to the previous server.
core.ResetClientState()
}
func (con *SliverClient) monitorConnectionLost(ctx context.Context, conn *grpc.ClientConn) {
if conn == nil {
return
}
for {
state := conn.GetState()
if state == connectivity.Idle {
// Mirror the previous behavior from cli/console.go, but only when not canceled.
select {
case <-ctx.Done():
return
default:
}
fmt.Println("\nLost connection to server. Exiting now.")
con.FlushOutput()
//nolint:forbidigo // Explicitly exits to match legacy behavior.
os.Exit(1)
}
if !conn.WaitForStateChange(ctx, state) {
return
}
}
}
+204 -152
View File
@@ -35,6 +35,7 @@ import (
consts "github.com/bishopfox/sliver/client/constants"
"github.com/bishopfox/sliver/client/core"
"github.com/bishopfox/sliver/client/spin"
"github.com/bishopfox/sliver/client/theme"
"github.com/bishopfox/sliver/client/version"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/commonpb"
@@ -46,6 +47,7 @@ import (
"github.com/reeflective/readline"
"github.com/spf13/cobra"
"golang.org/x/exp/slog"
"google.golang.org/grpc"
"google.golang.org/protobuf/proto"
)
@@ -54,32 +56,10 @@ const (
)
const (
// ANSI Colors.
Normal = "\033[0m"
Black = "\033[30m"
Red = "\033[31m"
Green = "\033[32m"
Orange = "\033[33m"
Blue = "\033[34m"
Purple = "\033[35m"
Cyan = "\033[36m"
Gray = "\033[37m"
Bold = "\033[1m"
Clearln = "\r\x1b[2K"
UpN = "\033[%dA"
DownN = "\033[%dB"
Underline = "\033[4m"
// Info - Display colorful information.
Info = Bold + Cyan + "[*] " + Normal
// Warn - Warn a user.
Warn = Bold + Red + "[!] " + Normal
// Debug - Display debug information.
Debug = Bold + Purple + "[-] " + Normal
// Woot - Display success.
Woot = Bold + Green + "[$] " + Normal
// Success - Diplay success.
Success = Bold + Green + "[+] " + Normal
// Terminal control sequences (not "styling").
Clearln = "\r\x1b[2K"
UpN = "\033[%dA"
DownN = "\033[%dB"
)
// Observer - A function to call when the sessions changes.
@@ -106,6 +86,21 @@ type SliverClient struct {
stdoutPipeWriter *os.File
stdoutPipeDone chan struct{}
stdoutPipeOnce sync.Once
connMu sync.Mutex
grpcConn *grpc.ClientConn
connDetails *ConnectionDetails
connCancel context.CancelFunc
connWg *sync.WaitGroup
connectionHooksApplied bool
logCommandHookApplied bool
// These writers are always safe to use (never error); the underlying stream
// can be swapped on server switch to avoid breaking io.MultiWriter/io.Copy.
jsonRemoteWriter *optionalRemoteWriter
asciicastRemoteWriter *optionalRemoteWriter
jsonRemoteStream rpcpb.SliverRPC_ClientLogClient
asciicastRemoteStream rpcpb.SliverRPC_ClientLogClient
}
// NewConsole creates the sliver client (and console), creating menus and prompts.
@@ -113,6 +108,8 @@ type SliverClient struct {
// thus has not started monitoring any server events, or started the application.
func NewConsole(isServer bool) *SliverClient {
assets.Setup(false, false)
_ = theme.LoadAndSetCurrentTheme()
ApplyTheme(theme.Current())
settings, _ := assets.LoadSettings()
con := &SliverClient{
@@ -126,7 +123,10 @@ func NewConsole(isServer bool) *SliverClient {
BeaconTaskCallbacksMutex: &sync.Mutex{},
IsServer: isServer,
Settings: settings,
connWg: &sync.WaitGroup{},
}
// Ensure logging never panics even if console logs are disabled.
con.jsonHandler = slog.NewJSONHandler(io.Discard, &slog.HandlerOptions{Level: slog.LevelDebug})
// The active target needs access to the console
// to automatically switch between command menus.
@@ -165,8 +165,7 @@ func NewConsole(isServer bool) *SliverClient {
// Init requires a working RPC connection to the sliver server, and 2 different sets of commands.
// If run is true, the console application is started, making this call blocking. Otherwise, commands and
// RPC connection are bound to the console (making the console ready to run), but the console does not start.
func StartClient(con *SliverClient, rpc rpcpb.SliverRPCClient, serverCmds, sliverCmds console.Commands, run bool, rcScript string) error {
con.Rpc = rpc
func StartClient(con *SliverClient, rpc rpcpb.SliverRPCClient, grpcConn *grpc.ClientConn, details *ConnectionDetails, serverCmds, sliverCmds console.Commands, run bool, rcScript string) error {
con.IsCLI = !run
con.serverCmds = serverCmds
con.sliverCmds = sliverCmds
@@ -188,39 +187,29 @@ 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)
con.applyConnectionHooksOnce()
// console logger
if con.Settings.ConsoleLogs {
// Classic logs
consoleLog := getConsoleLogFile()
consoleLogStream, err := con.ClientLogStream("json")
if err != nil {
log.Printf("Could not get client json log stream: %s", err)
}
con.setupLogger(consoleLog, consoleLogStream)
con.ensureJSONRemoteWriter()
con.setupLogger(consoleLog, con.jsonRemoteWriter)
defer consoleLog.Close()
// Ascii cast sessions (complete terminal interface).
asciicastLog := getConsoleAsciicastFile()
defer asciicastLog.Close()
// Ascii cast sessions (complete terminal interface) are only useful
// for the interactive console. In CLI mode they would clobber stdout.
if !con.IsCLI {
asciicastLog := getConsoleAsciicastFile()
defer asciicastLog.Close()
asciicastStream, err := con.ClientLogStream("asciicast")
if err != nil {
log.Printf("Could not get client asciicast log stream: %s", err)
con.ensureAsciicastRemoteWriter()
con.setupAsciicastRecord(asciicastLog, con.asciicastRemoteWriter)
}
con.setupAsciicastRecord(asciicastLog, asciicastStream)
}
if err := con.SetConnection(rpc, grpcConn, details); err != nil {
return err
}
if rcScript != "" {
@@ -319,10 +308,29 @@ func (con *SliverClient) runRCLine(serverCmds, sliverCmds console.Commands, line
return nil
}
func (con *SliverClient) startEventLoop() {
eventStream, err := con.Rpc.Events(context.Background(), &commonpb.Empty{})
func (con *SliverClient) applyConnectionHooksOnce() {
con.connMu.Lock()
defer con.connMu.Unlock()
if con.connectionHooksApplied {
return
}
con.connectionHooksApplied = true
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)
}
}
}
func (con *SliverClient) startEventLoop(ctx context.Context) {
eventStream, err := con.Rpc.Events(ctx, &commonpb.Empty{})
if err != nil {
fmt.Printf(Warn+"%s\n", err)
fmt.Printf("%s%s\n", Warn, err)
return
}
for {
@@ -337,7 +345,7 @@ func (con *SliverClient) startEventLoop() {
switch event.EventType {
case consts.CanaryEvent:
con.PrintEventErrorf(Bold+"WARNING: %s%s has been burned (DNS Canary)", Normal, event.Session.Name)
con.PrintEventErrorf("%s %s has been burned (DNS Canary)", StyleBold.Render("WARNING:"), event.Session.Name)
sessions := con.GetSessionsByName(event.Session.Name)
for _, session := range sessions {
shortID := strings.Split(session.ID, "-")[0]
@@ -346,7 +354,7 @@ func (con *SliverClient) startEventLoop() {
case consts.WatchtowerEvent:
msg := string(event.Data)
con.PrintEventErrorf(Bold+"WARNING: %s%s has been burned (seen on %s)", Normal, event.Session.Name, msg)
con.PrintEventErrorf("%s %s has been burned (seen on %s)", StyleBold.Render("WARNING:"), event.Session.Name, msg)
sessions := con.GetSessionsByName(event.Session.Name)
for _, session := range sessions {
shortID := strings.Split(session.ID, "-")[0]
@@ -462,7 +470,7 @@ func (con *SliverClient) triggerReactions(event *clientpb.Event) {
for _, reaction := range reactions {
for _, line := range reaction.Commands {
con.PrintInfof(Bold+"Execute reaction: '%s'"+Normal, line)
con.PrintInfof("%s '%s'", StyleBold.Render("Execute reaction:"), line)
err := con.App.ActiveMenu().RunCommandLine(context.Background(), line)
if err != nil {
con.PrintErrorf("Reaction command error: %s\n", err)
@@ -514,41 +522,107 @@ func (con *SliverClient) AddBeaconCallback(taskID string, callback BeaconTaskCal
}
func (con *SliverClient) GetPrompt() string {
prompt := Underline + "sliver" + Normal
if con.IsServer {
prompt = Bold + "[server] " + Normal + Underline + "sliver" + Normal
promptStyle := assets.PromptStyleHost
if con.Settings != nil {
promptStyle = assets.NormalizePromptStyle(con.Settings.PromptStyle)
}
if con.ActiveTarget.GetSession() != nil {
prompt += fmt.Sprintf(Bold+Red+" (%s)%s", con.ActiveTarget.GetSession().Name, Normal)
} else if con.ActiveTarget.GetBeacon() != nil {
prompt += fmt.Sprintf(Bold+Blue+" (%s)%s", con.ActiveTarget.GetBeacon().Name, Normal)
if promptStyle == assets.PromptStyleCustom {
src := assets.DefaultPromptTemplate
if con.Settings != nil && strings.TrimSpace(con.Settings.PromptTemplate) != "" {
src = con.Settings.PromptTemplate
}
out, err := renderPromptTemplate(con, src)
if err != nil {
return Clearln + " > "
}
if strings.TrimSpace(out) == "" {
return Clearln + " > "
}
return Clearln + out
}
prompt := StyleUnderline.Render("sliver")
if con.IsServer {
if promptStyle != assets.PromptStyleBasic {
prompt = StyleBold.Render("[server]") + " " + prompt
}
} else if promptStyle != assets.PromptStyleBasic {
// sliver-client only: optionally include operator/host context.
if details, _, ok := con.CurrentConnection(); ok && details != nil && details.Config != nil {
operator := strings.TrimSpace(details.Config.Operator)
host := strings.TrimSpace(details.Config.LHost)
prefix := ""
switch promptStyle {
case assets.PromptStyleOperatorHost:
if operator != "" && host != "" {
prefix = operator + "@" + host
} else if operator != "" {
prefix = operator
} else if host != "" {
prefix = host
}
case assets.PromptStyleHost:
if host != "" {
prefix = host
}
}
if prefix != "" {
prompt = StyleBoldPrimary.Render("["+prefix+"]") + " " + prompt
}
}
}
if session := con.ActiveTarget.GetSession(); session != nil {
prompt += StyleBoldRed.Render(" (" + session.Name + ")")
} else if beacon := con.ActiveTarget.GetBeacon(); beacon != nil {
prompt += StyleBoldBlue.Render(" (" + beacon.Name + ")")
}
prompt += " > "
return Clearln + prompt
}
func (con *SliverClient) PrintLogo() {
serverVer, err := con.Rpc.GetVersion(context.Background(), &commonpb.Empty{})
// Always show a logo even if the server connection is transiently unavailable.
logo := asciiLogos[util.Intn(len(asciiLogos))]
fmt.Println(strings.ReplaceAll(logo.Render(), "\n", "\r\n"))
fmt.Println("All hackers gain " + abilities[util.Intn(len(abilities))] + "\r")
if con.Rpc == nil {
fmt.Println(Warn + "Not connected to a server\r")
fmt.Printf("%sClient %s\r\n", Info, version.FullVersion())
fmt.Println(Info + "Welcome to the sliver shell, please type 'help' for options\r")
fmt.Println("")
return
}
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
serverVer, err := con.Rpc.GetVersion(ctx, &commonpb.Empty{})
if err != nil {
panic(err.Error())
fmt.Printf("%sCould not query server version: %s\r\n", Warn, err)
fmt.Printf("%sClient %s\r\n", Info, version.FullVersion())
fmt.Println(Info + "Welcome to the sliver shell, please type 'help' for options\r")
fmt.Println("")
con.CheckLastUpdate()
return
}
dirty := ""
if serverVer.Dirty {
dirty = fmt.Sprintf(" - %sDirty%s", Bold, Normal)
dirty = " - " + StyleBold.Render("Dirty")
}
serverSemVer := fmt.Sprintf("%d.%d.%d", serverVer.Major, serverVer.Minor, serverVer.Patch)
logo := asciiLogos[util.Intn(len(asciiLogos))]
fmt.Println(strings.ReplaceAll(logo, "\n", "\r\n"))
fmt.Println("All hackers gain " + abilities[util.Intn(len(abilities))] + "\r")
fmt.Printf(Info+"Server v%s - %s%s\r\n", serverSemVer, serverVer.Commit, dirty)
fmt.Printf("%sServer v%s - %s%s\r\n", Info, serverSemVer, serverVer.Commit, dirty)
if version.GitCommit != serverVer.Commit {
fmt.Printf(Info+"Client %s\r\n", version.FullVersion())
fmt.Printf("%sClient %s\r\n", Info, version.FullVersion())
}
fmt.Println(Info + "Welcome to the sliver shell, please type 'help' for options\r")
if serverVer.Major != int32(version.SemanticVersion()[0]) {
fmt.Printf(Warn + "Warning: Client and server may be running incompatible versions.\r\n")
fmt.Print(Warn + "Warning: Client and server may be running incompatible versions.\r\n")
}
fmt.Println("")
con.CheckLastUpdate()
}
@@ -564,7 +638,7 @@ func (con *SliverClient) 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")
con.Printf("%sCheck for updates with the 'update' command\n\n", Info)
}
}
}
@@ -604,7 +678,7 @@ func (con *SliverClient) GetSession(arg string) *clientpb.Session {
func (con *SliverClient) GetSessionsByName(name string) []*clientpb.Session {
sessions, err := con.Rpc.GetSessions(context.Background(), &commonpb.Empty{})
if err != nil {
fmt.Printf(Warn+"%s\n", err)
fmt.Printf("%s%s\n", Warn, err)
return nil
}
matched := []*clientpb.Session{}
@@ -745,7 +819,7 @@ func (con *SliverClient) FormatDateDelta(t time.Time, includeDate bool, color bo
interval = time.Since(t).Round(time.Second).String()
}
if color {
interval = fmt.Sprintf("%s%s%s", Bold+Red, interval, Normal)
interval = StyleBoldRed.Render(interval)
}
} else {
if includeDate {
@@ -754,7 +828,7 @@ func (con *SliverClient) FormatDateDelta(t time.Time, includeDate bool, color bo
interval = time.Until(t).Round(time.Second).String()
}
if color {
interval = fmt.Sprintf("%s%s%s", Bold+Green, interval, Normal)
interval = StyleBoldGreen.Render(interval)
}
}
return interval
@@ -789,7 +863,7 @@ type ActiveTarget struct {
// GetSessionInteractive - Get the active target(s).
func (s *ActiveTarget) GetInteractive() (*clientpb.Session, *clientpb.Beacon) {
if s.session == nil && s.beacon == nil {
fmt.Printf(Warn + "Please select a session or beacon via `use`\n")
fmt.Print(Warn + "Please select a session or beacon via `use`\n")
return nil, nil
}
return s.session, s.beacon
@@ -803,7 +877,7 @@ func (s *ActiveTarget) Get() (*clientpb.Session, *clientpb.Beacon) {
// GetSessionInteractive - GetSessionInteractive the active session.
func (s *ActiveTarget) GetSessionInteractive() *clientpb.Session {
if s.session == nil {
fmt.Printf(Warn + "Please select a session via `use`\n")
fmt.Print(Warn + "Please select a session via `use`\n")
return nil
}
return s.session
@@ -817,7 +891,7 @@ func (s *ActiveTarget) GetSession() *clientpb.Session {
// GetBeaconInteractive - Get beacon interactive the active session.
func (s *ActiveTarget) GetBeaconInteractive() *clientpb.Beacon {
if s.beacon == nil {
fmt.Printf(Warn + "Please select a beacon via `use`\n")
fmt.Print(Warn + "Please select a beacon via `use`\n")
return nil
}
return s.beacon
@@ -1037,82 +1111,60 @@ var abilities = []string{
"jump-start",
}
var asciiLogos = []string{
Red + `
██████ ██▓ ██▓ ██▒ █▓▓█████ ██▀███
▒██ ▒ ▓██▒ ▓██▒▓██░ █▒▓█ ▀ ▓██ ▒ ██▒
░ ▓██▄ ▒██░ ▒██▒ ▓██ █▒░▒███ ▓██ ░▄█ ▒
▒ ██▒▒██░ ░██░ ▒██ █░░▒▓█ ▄ ▒██▀▀█▄
▒██████▒▒░██████▒░██░ ▒▀█░ ░▒████▒░██▓ ▒██▒
▒ ▒▓▒ ▒ ░░ ▒░▓ ░░▓ ░ ▐░ ░░ ▒░ ░░ ▒▓ ░▒▓░
░ ░▒ ░ ░░ ░ ▒ ░ ▒ ░ ░ ░░ ░ ░ ░ ░▒ ░ ▒░
░ ░ ░ ░ ░ ▒ ░ ░░ ░ ░░ ░
░ ░ ░ ░ ░ ░ ░ ░
` + Normal,
type asciiLogoStyle int
Green + `
███████╗██╗ ██╗██╗ ██╗███████╗██████╗
██╔════╝██║ ██║██║ ██║██╔════╝██╔══██╗
███████╗██║ ██║██║ ██║█████╗ ██████╔╝
╚════██║██║ ██║╚██╗ ██╔╝██╔══╝ ██╔══██╗
███████║███████╗██║ ╚████╔╝ ███████╗██║ ██║
╚══════╝╚══════╝╚═╝ ╚═══╝ ╚══════╝╚═╝ ╚═╝
` + Normal,
const (
logoStyleRed asciiLogoStyle = iota
logoStyleGreen
logoStyleBoldGray
)
Bold + Gray + `
type asciiLogo struct {
style asciiLogoStyle
art string
}
func (l asciiLogo) Render() string {
switch l.style {
case logoStyleRed:
return StyleRed.Render(l.art)
case logoStyleGreen:
return StyleGreen.Render(l.art)
case logoStyleBoldGray:
return StyleBoldGray.Render(l.art)
default:
return l.art
}
}
var asciiLogos = []asciiLogo{
{style: logoStyleRed, art: `
██████ ██▓ ██▓ ██▒ █▓▓█████ ██▀███
▒██ ▒ ▓██▒ ▓██▒▓██░ █▒▓█ ▀ ▓██ ▒ ██▒
░ ▓██▄ ▒██░ ▒██▒ ▓██ █▒░▒███ ▓██ ░▄█ ▒
▒ ██▒▒██░ ░██░ ▒██ █░░▒▓█ ▄ ▒██▀▀█▄
▒██████▒▒░██████▒░██░ ▒▀█░ ░▒████▒░██▓ ▒██▒
▒ ▒▓▒ ▒ ░░ ▒░▓ ░░▓ ░ ▐░ ░░ ▒░ ░░ ▒▓ ░▒▓░
░ ░▒ ░ ░░ ░ ▒ ░ ▒ ░ ░ ░░ ░ ░ ░ ░▒ ░ ▒░
░ ░ ░ ░ ░ ▒ ░ ░░ ░ ░░ ░
░ ░ ░ ░ ░ ░ ░ ░
`},
{style: logoStyleGreen, art: `
███████╗██╗ ██╗██╗ ██╗███████╗██████╗
██╔════╝██║ ██║██║ ██║██╔════╝██╔══██╗
███████╗██║ ██║██║ ██║█████╗ ██████╔╝
╚════██║██║ ██║╚██╗ ██╔╝██╔══╝ ██╔══██╗
███████║███████╗██║ ╚████╔╝ ███████╗██║ ██║
╚══════╝╚══════╝╚═╝ ╚═══╝ ╚══════╝╚═╝ ╚═╝
`},
{style: logoStyleBoldGray, art: `
.------..------..------..------..------..------.
|S.--. ||L.--. ||I.--. ||V.--. ||E.--. ||R.--. |
| :/\: || :/\: || (\/) || :(): || (\/) || :(): |
| :\/: || (__) || :\/: || ()() || :\/: || ()() |
| '--'S|| '--'L|| '--'I|| '--'V|| '--'E|| '--'R|
` + "`------'`------'`------'`------'`------'`------'" + `
` + Normal,
Purple + `
****@@ @@****
@@@@@@***@ @***@@@@@@
@%%@@@%%#***@ @***#%%@@@%%@
%%%%%##%%%%****@ @****%%%%##%%%%%
%%%%#####%%%%#*###@ @#####%%%%#####%%%%
@%%%*@#####%%%%@####@ @####@%%%%#####@#%%%@
%%%+@**#####%%%%#####@ @#####%%%%#####**@+%%%@
#%%=+*+**###%#%%%###### ######%%%%%###**+*+=%%%
%#%===++@*####%@@@@###### ######@@@@%####*@++===%%%
@#@--===+**%%%@@@@@@#####%% %%#####@@@@@@%%#**+===--@#@
#@----%=+*%%%%@@@%%%####%%%@ @ @ @%%%####%%%@@@%%%%*+=%----@#
#%----=#++*%%%%@%%%%%@%%%%%%%%%@ @%%%%%%%%%@%%%%%@%%%%*++#=----%#
%+----===+**@%%@#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%@%%@**+===----+%
@=----==++***@%@#%%%%%%%@%%%%@%%%%%%%%%%%%%%%%%%%%@%%%%@%%%%%%%#@%@***++==-----@
-----==+#%**##@@%%%@%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%@%%%@@##**%#+==-----
%----==+**%%%%@@@#@%%%%%%%%%%@%%%%%%%%%%%%%%%%%%@%%%%%%%%%%@#@@@%%%%*++==----%
=--#+=+**#@%%####%####%%%%@@@@@%%%%%%##%%%%%%@@@@@@%%%####%####%%@#**+=+#--=
+-==+*%%%%########%#######%@@@@@@%%%%%#%%@@@@@@%#######%########%%%%*+==-=
-----==++**#%######################%@@@%%%%@@@%######################%#**++==-----
=--===+**######@##############%%%%%%%%%%%%%%%%%%%%##############@######**+===--=
%-==+%@@@#######%%%%%%%%%@@%%%%%%%%%%%%%%%%%%%%%%%%@@%%%%%%%%%#######@@@%+==-%
@=@@@@@@@@#%%%%%%%%%%%%%%@@%%%%%%%%%%%%%%%%%%%%%%@@@%%%%%%%%%%%%%#@@@@@@@@=@
@@@@@@@@@@@@@%%%%%%%%%%%%@@@@%%%%%%%%**%%%%%%%%@@@@%%%%%%%%%%%%@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@%%#%@@%%%%@@@@@%%%%%@#**#@%%%%%@@@@@%%%%@@%#%%@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@%#####%#%%%%%%%@%###**###%@%%%%%%%#######%@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@*##%%%%%%%%%%%%%##**##%%%%%%%%%%%%###*@@@@@@@@@@@@@@@@@@@@@@@
@%%%%%@@@@@@@@@@@@@@@@@@@ %%%%%%%%%%%%%##**##%%%%%%%%%%%%% @@@@@@@@@@@@@@@@@@@%%%%%@
@%%%%%%%%%%%%%%@@@@@@@@@@@@ . %%%%%@%%%%%##**##%%%%%@%%%%% @@@@@@@@@@@@%%%%%%%%%%%%%%@
%%%%%%%@+. % :@%%%%%%%%@@@@@@@@@%@%%@@@@##**##@@@@%%@%@@@@@@@@@%%%%%%%%@: % ..+@%%%%%%%
%#. . : .-@%%%%%@@@@@@@@@@@@@@##**##@@@@@@@@@@@@@@%%%%%@-. : .*%
@%%@* #..:--=++*@%@@@@@@@@@@@@##**##@@@@@@@@@@@@@@*++==-:. # *@%%@
%%%%%%@..*.:+% ..::+-==++++@-=@@@@@@@@@@@%####%@@@@@@@@@@@=-@++++==-+::.. #+:.*..@%%%%%%
@###%.......::%-==++**#@++#*: .:-+@@@@@@@@@@%####%@@@@@@@@@@+-:. .*#++@#**++==-%::.......%###@
#%..........:::-==%+*******@* . .:-+@@@@@@@@@@####@@@@@@@@@@+-: #@*******+%=--:::..........%#
@@@+:...::::--=++**@***##*==+ :-+@@@.@@@@%%##%%@@@@.@@@+-: ++=*##***@**++=--::::...:+@@@
@%%%***###%@#****##@##++++++. .:=*@+ @@@@%%##%%@@@@ =@*=:. .+++++=##@##****#@%###***%%%@
@@=====+++++***#####%%%%@++++**#.:=*@ .@@%%####%%@@. @*=:.#***+++@%%%%#####***+++++=====@@
@@@#+++++***##%%%%%***##%%*# @@*------*@@ #*%%##***%%%%%##***+++++#@@@
@@@@%%%@%%%%%. **--------** .%%%%%%%%%@@@@
@%%%% %@@@@@@@@@@@@@ %%%%@
@@ @::@@@@@@@@@@::@ @@
-::::::@@@@:::::::
*-----========-----+
@###%%%@@@@%%%###@
@%%%%%%@
` + Normal,
`},
}
+111 -2
View File
@@ -26,6 +26,7 @@ import (
"os"
"path/filepath"
"strings"
"sync"
"time"
"github.com/bishopfox/sliver/client/assets"
@@ -51,6 +52,109 @@ func (l *ConsoleClientLogger) Write(buf []byte) (int, error) {
return len(buf), err
}
// optionalRemoteWriter is an io.Writer that forwards to an optional remote writer.
// It always returns len(buf), nil to avoid breaking io.MultiWriter/io.Copy when
// a remote stream disconnects during a server switch.
type optionalRemoteWriter struct {
mu sync.RWMutex
w io.Writer
}
func (o *optionalRemoteWriter) Set(w io.Writer) {
o.mu.Lock()
o.w = w
o.mu.Unlock()
}
func (o *optionalRemoteWriter) Write(buf []byte) (int, error) {
o.mu.RLock()
w := o.w
o.mu.RUnlock()
if w != nil {
_, _ = w.Write(buf)
}
return len(buf), nil
}
func (con *SliverClient) ensureJSONRemoteWriter() {
con.connMu.Lock()
defer con.connMu.Unlock()
if con.jsonRemoteWriter == nil {
con.jsonRemoteWriter = &optionalRemoteWriter{}
}
}
func (con *SliverClient) ensureAsciicastRemoteWriter() {
con.connMu.Lock()
defer con.connMu.Unlock()
if con.asciicastRemoteWriter == nil {
con.asciicastRemoteWriter = &optionalRemoteWriter{}
}
}
func (con *SliverClient) refreshRemoteLogStreamsLocked() {
// Only refresh if the console logging stack has been initialized.
if con.jsonRemoteWriter == nil && con.asciicastRemoteWriter == nil {
return
}
if con.Rpc == nil {
con.setRemoteLogStreamsLocked(nil, nil)
return
}
var jsonStream *ConsoleClientLogger
var asciicastStream *ConsoleClientLogger
if con.jsonRemoteWriter != nil {
s, err := con.ClientLogStream("json")
if err != nil {
log.Printf("Could not get client json log stream: %s", err)
} else {
jsonStream = s
}
}
if con.asciicastRemoteWriter != nil {
s, err := con.ClientLogStream("asciicast")
if err != nil {
log.Printf("Could not get client asciicast log stream: %s", err)
} else {
asciicastStream = s
}
}
con.setRemoteLogStreamsLocked(jsonStream, asciicastStream)
}
func (con *SliverClient) setRemoteLogStreamsLocked(jsonStream, asciicastStream *ConsoleClientLogger) {
// Detach writers first so background pipes can't hit a closing stream.
if con.jsonRemoteWriter != nil {
con.jsonRemoteWriter.Set(nil)
}
if con.asciicastRemoteWriter != nil {
con.asciicastRemoteWriter.Set(nil)
}
if con.jsonRemoteStream != nil {
_ = con.jsonRemoteStream.CloseSend()
con.jsonRemoteStream = nil
}
if con.asciicastRemoteStream != nil {
_ = con.asciicastRemoteStream.CloseSend()
con.asciicastRemoteStream = nil
}
if jsonStream != nil && con.jsonRemoteWriter != nil {
con.jsonRemoteWriter.Set(jsonStream)
con.jsonRemoteStream = jsonStream.Stream
}
if asciicastStream != nil && con.asciicastRemoteWriter != nil {
con.asciicastRemoteWriter.Set(asciicastStream)
con.asciicastRemoteStream = asciicastStream.Stream
}
}
// ClientLogStream requires a log stream name, used to save the logs
// going through this stream in a specific log subdirectory/file.
func (con *SliverClient) ClientLogStream(name string) (*ConsoleClientLogger, error) {
@@ -69,7 +173,12 @@ func (con *SliverClient) setupLogger(writers ...io.Writer) {
con.jsonHandler = slog.NewJSONHandler(logWriter, jsonOptions)
// Log all commands before running them.
con.App.PreCmdRunLineHooks = append(con.App.PreCmdRunLineHooks, con.logCommand)
con.connMu.Lock()
defer con.connMu.Unlock()
if !con.logCommandHookApplied {
con.App.PreCmdRunLineHooks = append(con.App.PreCmdRunLineHooks, con.logCommand)
con.logCommandHookApplied = true
}
}
// logCommand logs non empty commands to the client log file.
@@ -242,7 +351,7 @@ func (con *SliverClient) PrintWarnf(format string, args ...any) {
logger.Warn(fmt.Sprintf(format, args...))
con.printf(Clearln+"⚠️ "+Normal+format, args...)
con.printf(Clearln+"⚠️ "+format, args...)
}
// PrintErrorf prints an error message immediately below the last line of output.
+216
View File
@@ -0,0 +1,216 @@
package console
import (
"bytes"
"fmt"
"strings"
"sync"
"text/template"
"time"
"github.com/bishopfox/sliver/client/theme"
"github.com/charmbracelet/lipgloss"
)
type promptPalette struct {
Default lipgloss.Color
Mods map[int]lipgloss.Color
}
type promptColors struct {
Primary promptPalette
Secondary promptPalette
Default promptPalette
Success promptPalette
Warning promptPalette
Danger promptPalette
}
type promptStyles struct {
Normal TextStyle
Bold TextStyle
Underline TextStyle
Primary TextStyle
Secondary TextStyle
Default TextStyle
Success TextStyle
Warning TextStyle
Danger TextStyle
BoldPrimary TextStyle
BoldSecondary TextStyle
BoldDefault TextStyle
BoldSuccess TextStyle
BoldWarning TextStyle
BoldDanger TextStyle
// Legacy names used across the codebase.
Black TextStyle
Red TextStyle
Green TextStyle
Orange TextStyle
Blue TextStyle
Purple TextStyle
Cyan TextStyle
Gray TextStyle
}
type promptTarget struct {
SessionName string
BeaconName string
// Suffix is a pre-rendered, themed " (name)" segment matching the default prompt.
Suffix string
}
type promptTemplateContext struct {
Now time.Time
IsServer bool
Connected bool
Operator string
Host string
Port int
HostPort string
Target promptTarget
Colors promptColors
Styles promptStyles
}
var promptTemplateCache struct {
mu sync.RWMutex
src string
tpl *template.Template
}
func toPromptPalette(p theme.Palette) promptPalette {
out := promptPalette{
Default: lipgloss.Color(p.Default),
Mods: map[int]lipgloss.Color{},
}
for k, v := range p.Mods {
out.Mods[k] = lipgloss.Color(v)
}
return out
}
func promptTemplateForSource(src string) (*template.Template, error) {
promptTemplateCache.mu.RLock()
if promptTemplateCache.tpl != nil && promptTemplateCache.src == src {
t := promptTemplateCache.tpl
promptTemplateCache.mu.RUnlock()
return t, nil
}
promptTemplateCache.mu.RUnlock()
t, err := template.New("sliver-prompt").Option("missingkey=zero").Parse(src)
if err != nil {
return nil, err
}
promptTemplateCache.mu.Lock()
promptTemplateCache.src = src
promptTemplateCache.tpl = t
promptTemplateCache.mu.Unlock()
return t, nil
}
func renderPromptTemplate(con *SliverClient, src string) (string, error) {
if con == nil {
return "", nil
}
details, _, ok := con.CurrentConnection()
operator := ""
host := ""
port := 0
hostPort := ""
if ok && details != nil && details.Config != nil {
operator = strings.TrimSpace(details.Config.Operator)
host = strings.TrimSpace(details.Config.LHost)
port = int(details.Config.LPort)
}
if host != "" && port != 0 {
hostPort = fmt.Sprintf("%s:%d", host, port)
}
// Active target context.
var suffix string
sessionName := ""
beaconName := ""
if session := con.ActiveTarget.GetSession(); session != nil {
sessionName = session.Name
suffix = StyleBoldRed.Render(" (" + sessionName + ")")
} else if beacon := con.ActiveTarget.GetBeacon(); beacon != nil {
beaconName = beacon.Name
suffix = StyleBoldBlue.Render(" (" + beaconName + ")")
}
th := theme.Current()
ctx := promptTemplateContext{
Now: time.Now(),
IsServer: con.IsServer,
Connected: ok && details != nil && details.Config != nil,
Operator: operator,
Host: host,
Port: port,
HostPort: hostPort,
Target: promptTarget{
SessionName: sessionName,
BeaconName: beaconName,
Suffix: suffix,
},
Colors: promptColors{
Primary: toPromptPalette(th.Primary),
Secondary: toPromptPalette(th.Secondary),
Default: toPromptPalette(th.Default),
Success: toPromptPalette(th.Success),
Warning: toPromptPalette(th.Warning),
Danger: toPromptPalette(th.Danger),
},
Styles: promptStyles{
Normal: StyleNormal,
Bold: StyleBold,
Underline: StyleUnderline,
Primary: StylePrimary,
Secondary: StyleSecondary,
Default: StyleDefault,
Success: StyleSuccess,
Warning: StyleWarning,
Danger: StyleDanger,
BoldPrimary: StyleBoldPrimary,
BoldSecondary: StyleBoldSecondary,
BoldDefault: StyleBoldDefault,
BoldSuccess: StyleBoldSuccess,
BoldWarning: StyleBoldWarning,
BoldDanger: StyleBoldDanger,
Black: StyleBlack,
Red: StyleRed,
Green: StyleGreen,
Orange: StyleOrange,
Blue: StyleBlue,
Purple: StylePurple,
Cyan: StyleCyan,
Gray: StyleGray,
},
}
tpl, err := promptTemplateForSource(src)
if err != nil {
return "", err
}
var buf bytes.Buffer
if err := tpl.Execute(&buf, ctx); err != nil {
return "", err
}
return buf.String(), nil
}
+133
View File
@@ -0,0 +1,133 @@
package console
import (
"github.com/bishopfox/sliver/client/theme"
"github.com/charmbracelet/lipgloss"
)
// TextStyle is the shared styling type for the client/server consoles.
// Keep this type in the console package so most callsites don't need to import
// lipgloss directly.
type TextStyle = lipgloss.Style
var (
StyleNormal = lipgloss.NewStyle()
StyleBold = lipgloss.NewStyle().Bold(true)
StyleUnderline = lipgloss.NewStyle().Underline(true)
// Theme palette styles.
StylePrimary = lipgloss.NewStyle()
StyleSecondary = lipgloss.NewStyle()
StyleDefault = lipgloss.NewStyle()
StyleSuccess = lipgloss.NewStyle()
StyleWarning = lipgloss.NewStyle()
StyleDanger = lipgloss.NewStyle()
StyleBoldPrimary = lipgloss.NewStyle()
StyleBoldSecondary = lipgloss.NewStyle()
StyleBoldDefault = lipgloss.NewStyle()
StyleBoldSuccess = lipgloss.NewStyle()
StyleBoldWarning = lipgloss.NewStyle()
StyleBoldDanger = lipgloss.NewStyle()
// Legacy color names used across the codebase.
StyleBlack = lipgloss.NewStyle()
StyleRed = lipgloss.NewStyle()
StyleGreen = lipgloss.NewStyle()
StyleOrange = lipgloss.NewStyle()
StyleBlue = lipgloss.NewStyle()
StylePurple = lipgloss.NewStyle()
StyleCyan = lipgloss.NewStyle()
StyleGray = lipgloss.NewStyle()
StyleBoldBlack = lipgloss.NewStyle()
StyleBoldRed = lipgloss.NewStyle()
StyleBoldGreen = lipgloss.NewStyle()
StyleBoldOrange = lipgloss.NewStyle()
StyleBoldBlue = lipgloss.NewStyle()
StyleBoldPurple = lipgloss.NewStyle()
StyleBoldCyan = lipgloss.NewStyle()
StyleBoldGray = lipgloss.NewStyle()
)
// Prefix tags used across the client/server consoles.
var (
Info string
Warn string
Debug string
Woot string
Success string
)
func init() {
// Apply a usable theme without doing any disk I/O.
ApplyTheme(theme.Current())
}
// ApplyTheme updates all exported styles/prefixes to match the provided theme.
//
// Note: this is intentionally a "global theme" for the console, since most of
// the existing code expects these styles to be package-level values.
func ApplyTheme(t theme.Theme) {
palette := func(p theme.Palette) lipgloss.Color {
if p.Default != "" {
return lipgloss.Color(p.Default)
}
return lipgloss.Color("#ffffff")
}
paletteMod := func(p theme.Palette, mod int) lipgloss.Color {
if mod != 0 {
if v, ok := p.Mods[mod]; ok && v != "" {
return lipgloss.Color(v)
}
}
return palette(p)
}
// Palette styles.
StylePrimary = lipgloss.NewStyle().Foreground(palette(t.Primary))
StyleSecondary = lipgloss.NewStyle().Foreground(palette(t.Secondary))
StyleDefault = lipgloss.NewStyle().Foreground(palette(t.Default))
StyleSuccess = lipgloss.NewStyle().Foreground(palette(t.Success))
StyleWarning = lipgloss.NewStyle().Foreground(palette(t.Warning))
StyleDanger = lipgloss.NewStyle().Foreground(palette(t.Danger))
StyleBoldPrimary = lipgloss.NewStyle().Bold(true).Foreground(palette(t.Primary))
StyleBoldSecondary = lipgloss.NewStyle().Bold(true).Foreground(palette(t.Secondary))
StyleBoldDefault = lipgloss.NewStyle().Bold(true).Foreground(palette(t.Default))
StyleBoldSuccess = lipgloss.NewStyle().Bold(true).Foreground(palette(t.Success))
StyleBoldWarning = lipgloss.NewStyle().Bold(true).Foreground(palette(t.Warning))
StyleBoldDanger = lipgloss.NewStyle().Bold(true).Foreground(palette(t.Danger))
// Legacy mapping.
StyleBlack = lipgloss.NewStyle().Foreground(paletteMod(t.Default, 50))
// ANSI "gray" (37) is typically light; map to the lightest neutral.
StyleGray = lipgloss.NewStyle().Foreground(paletteMod(t.Default, 900))
StyleBlue = StylePrimary
// Keep Cyan distinct from Blue using a lighter primary tint.
StyleCyan = lipgloss.NewStyle().Foreground(paletteMod(t.Primary, 500))
StylePurple = StyleSecondary
StyleGreen = StyleSuccess
StyleOrange = StyleWarning
StyleRed = StyleDanger
StyleBoldBlack = lipgloss.NewStyle().Bold(true).Foreground(paletteMod(t.Default, 50))
StyleBoldGray = lipgloss.NewStyle().Bold(true).Foreground(paletteMod(t.Default, 900))
StyleBoldBlue = StyleBoldPrimary
StyleBoldCyan = lipgloss.NewStyle().Bold(true).Foreground(paletteMod(t.Primary, 500))
StyleBoldPurple = StyleBoldSecondary
StyleBoldGreen = StyleBoldSuccess
StyleBoldOrange = StyleBoldWarning
StyleBoldRed = StyleBoldDanger
// Prefix tags.
Info = StyleBoldCyan.Render("[*] ")
Warn = StyleBoldDanger.Render("[!] ")
Debug = StyleBoldSecondary.Render("[-] ")
Woot = StyleBoldSuccess.Render("[$] ")
Success = StyleBoldSuccess.Render("[+] ")
}
+39
View File
@@ -0,0 +1,39 @@
package core
import "net"
// ResetClientState tears down singleton client-side state that is tied to a
// specific server connection (tunnels, portfwds, SOCKS, cursed procs, etc).
//
// This is primarily used to support switching servers inside a single
// sliver-client process.
func ResetClientState() {
// Port forwards
for _, meta := range Portfwds.List() {
Portfwds.Remove(meta.ID)
}
// SOCKS
for _, meta := range SocksProxies.List() {
SocksProxies.Remove(meta.ID)
}
SocksConnPool.Range(func(key, value any) bool {
if conn, ok := value.(net.Conn); ok {
_ = conn.Close()
}
SocksConnPool.Delete(key)
return true
})
// Tunnels
GetTunnels().Reset()
// Cursed processes
CursedProcesses.Range(func(key, value any) bool {
if cp, ok := value.(*CursedProcess); ok && cp != nil && cp.PortFwd != nil {
Portfwds.Remove(cp.PortFwd.ID)
}
CursedProcesses.Delete(key)
return true
})
}
+9 -5
View File
@@ -20,6 +20,7 @@ package core
import (
"context"
"errors"
"io"
"log"
@@ -27,21 +28,21 @@ import (
)
// TunnelLoop - Parses incoming tunnel messages and distributes them
// to session/tunnel objects
// Expected to be called only once during initialization
func TunnelLoop(rpc rpcpb.SliverRPCClient) error {
//
// to session/tunnel objects
// Expected to be called only once during initialization
func TunnelLoop(ctx context.Context, rpc rpcpb.SliverRPCClient) error {
log.Println("Starting tunnel data loop ...")
defer log.Printf("Warning: TunnelLoop exited")
ctx, cancel := context.WithCancel(context.Background())
stream, err := rpc.TunnelData(ctx)
defer cancel()
if err != nil {
return err
}
GetTunnels().SetStream(stream)
defer GetTunnels().SetStream(nil)
for {
log.Printf("Waiting for TunnelData ...")
@@ -54,6 +55,9 @@ func TunnelLoop(rpc rpcpb.SliverRPCClient) error {
return nil
}
if err != nil {
if errors.Is(err, context.Canceled) {
return nil
}
log.Printf("Tunnel data read error: %s", err)
return err
}
+16
View File
@@ -160,3 +160,19 @@ func (t *tunnels) CloseForSession(sessionID string) {
}
}
}
// Reset closes all open tunnels and clears the underlying storage/stream.
// This is used when switching servers inside a single client process.
func (t *tunnels) Reset() {
t.SetStream(nil)
t.mutex.Lock()
defer t.mutex.Unlock()
for tunnelID, tunnel := range *t.tunnels {
if tunnel != nil {
_ = tunnel.Close()
}
delete(*t.tunnels, tunnelID)
}
}
+2 -1
View File
@@ -3,6 +3,7 @@ package forms
import (
"errors"
"github.com/bishopfox/sliver/client/theme"
"github.com/charmbracelet/huh"
)
@@ -44,7 +45,7 @@ func ArmoryInstallForm(options []ArmoryInstallOption) (*ArmoryInstallFormResult,
Height(listHeight(len(selectOptions)) + 2).
Value(&result.SelectedNames)
form := huh.NewForm(huh.NewGroup(field))
form := huh.NewForm(huh.NewGroup(field)).WithTheme(theme.HuhTheme())
if err := form.Run(); err != nil {
return nil, err
}
+2 -1
View File
@@ -3,6 +3,7 @@ package forms
import (
"errors"
"github.com/bishopfox/sliver/client/theme"
"github.com/charmbracelet/huh"
)
@@ -43,7 +44,7 @@ func ArmoryUpdateForm(options []ArmoryUpdateOption) (*ArmoryUpdateFormResult, er
Height(listHeight(len(selectOptions)) + 2).
Value(&result.SelectedIDs)
form := huh.NewForm(huh.NewGroup(field))
form := huh.NewForm(huh.NewGroup(field)).WithTheme(theme.HuhTheme())
if err := form.Run(); err != nil {
return nil, err
}
+2
View File
@@ -4,6 +4,7 @@ import (
"errors"
"strings"
"github.com/bishopfox/sliver/client/theme"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/util"
"github.com/charmbracelet/huh"
@@ -124,6 +125,7 @@ func GenerateForm(compiler *clientpb.Compiler) (*GenerateFormResult, error) {
),
)
form = form.WithTheme(theme.HuhTheme())
if err := form.Run(); err != nil {
return nil, err
}
+2
View File
@@ -5,6 +5,7 @@ import (
"strconv"
"strings"
"github.com/bishopfox/sliver/client/theme"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/util"
"github.com/charmbracelet/huh"
@@ -154,6 +155,7 @@ func GenerateBeaconForm(compiler *clientpb.Compiler) (*GenerateBeaconFormResult,
),
)
form = form.WithTheme(theme.HuhTheme())
if err := form.Run(); err != nil {
return nil, err
}
+2
View File
@@ -4,6 +4,7 @@ import (
"errors"
"strings"
"github.com/bishopfox/sliver/client/theme"
"github.com/bishopfox/sliver/util"
"github.com/charmbracelet/huh"
)
@@ -134,6 +135,7 @@ func GenerateProfilesNewForm() (*GenerateProfilesNewFormResult, error) {
),
)
form = form.WithTheme(theme.HuhTheme())
if err := form.Run(); err != nil {
return nil, err
}
@@ -4,6 +4,7 @@ import (
"errors"
"strings"
"github.com/bishopfox/sliver/client/theme"
"github.com/bishopfox/sliver/util"
"github.com/charmbracelet/huh"
)
@@ -166,6 +167,7 @@ func GenerateProfilesNewBeaconForm() (*GenerateProfilesNewBeaconFormResult, erro
),
)
form = form.WithTheme(theme.HuhTheme())
if err := form.Run(); err != nil {
return nil, err
}
+10 -5
View File
@@ -5,6 +5,7 @@ import (
"slices"
"strings"
"github.com/bishopfox/sliver/client/theme"
"github.com/charmbracelet/huh"
"golang.org/x/term"
)
@@ -32,7 +33,7 @@ func Confirm(title string, value *bool) error {
Title(title).
Value(value),
),
)
).WithTheme(theme.HuhTheme())
return form.Run()
}
@@ -49,7 +50,7 @@ func Input(title string, value *string) error {
Title(title).
Value(value),
),
)
).WithTheme(theme.HuhTheme())
return form.Run()
}
@@ -67,7 +68,7 @@ func Text(title string, value *string) error {
ExternalEditor(false).
Value(value),
),
)
).WithTheme(theme.HuhTheme())
return form.Run()
}
@@ -98,7 +99,9 @@ func MultiSelect(title string, options []string, value *[]string) error {
Height(listHeight(len(options)) + 1).
Value(value)
form := huh.NewForm(huh.NewGroup(field)).WithWidth(getTerminalWidth())
form := huh.NewForm(huh.NewGroup(field)).
WithTheme(theme.HuhTheme()).
WithWidth(getTerminalWidth())
return form.Run()
}
@@ -130,7 +133,9 @@ func selectPrompt(title string, options []string, value *string, required bool)
})
}
form := huh.NewForm(huh.NewGroup(field)).WithWidth(getTerminalWidth())
form := huh.NewForm(huh.NewGroup(field)).
WithTheme(theme.HuhTheme()).
WithWidth(getTerminalWidth())
err := form.Run()
// On error restore the originalValue and return err
+18
View File
@@ -6,6 +6,7 @@ import (
"strings"
"github.com/bishopfox/sliver/client/assets"
"github.com/bishopfox/sliver/client/theme"
"github.com/charmbracelet/huh"
)
@@ -19,6 +20,7 @@ type SettingsFormResult struct {
VimMode bool
UserConnect bool
ConsoleLogs bool
PromptStyle string
}
// SettingsForm prompts for client settings and returns the collected values.
@@ -39,10 +41,14 @@ func SettingsForm(settings *assets.ClientSettings, tableStyleOptions []string) (
VimMode: settings.VimMode,
UserConnect: settings.UserConnect,
ConsoleLogs: settings.ConsoleLogs,
PromptStyle: assets.NormalizePromptStyle(settings.PromptStyle),
}
if result.TableStyle == "" {
result.TableStyle = tableStyleOptions[0]
}
if result.PromptStyle == "" {
result.PromptStyle = assets.PromptStyleHost
}
styleOptions := make([]huh.Option[string], 0, len(tableStyleOptions))
for _, option := range tableStyleOptions {
@@ -51,6 +57,17 @@ func SettingsForm(settings *assets.ClientSettings, tableStyleOptions []string) (
form := huh.NewForm(
huh.NewGroup(
huh.NewSelect[string]().
Title("Prompt style").
Description("Customize the sliver prompt prefix").
Options(
huh.NewOption(assets.PromptStyleHost, assets.PromptStyleHost),
huh.NewOption(assets.PromptStyleOperatorHost, assets.PromptStyleOperatorHost),
huh.NewOption(assets.PromptStyleBasic, assets.PromptStyleBasic),
huh.NewOption(assets.PromptStyleCustom, assets.PromptStyleCustom),
).
Height(4).
Value(&result.PromptStyle),
huh.NewSelect[string]().
Title("Table style").
Options(styleOptions...).
@@ -90,6 +107,7 @@ func SettingsForm(settings *assets.ClientSettings, tableStyleOptions []string) (
).Title("Behavior"),
)
form = form.WithTheme(theme.HuhTheme())
if err := form.Run(); err != nil {
return nil, err
}
+2 -15
View File
@@ -10,21 +10,8 @@ import (
)
const (
// ANSI Colors
normal = "\033[0m"
black = "\033[30m"
red = "\033[31m"
green = "\033[32m"
orange = "\033[33m"
blue = "\033[34m"
purple = "\033[35m"
cyan = "\033[36m"
gray = "\033[37m"
bold = "\033[1m"
clearln = "\r\x1b[2K"
upN = "\033[%dA"
downN = "\033[%dB"
underline = "\033[4m"
// Terminal control sequence for clearing the current line.
clearln = "\r\x1b[2K"
)
// Spinner types.
+98
View File
@@ -0,0 +1,98 @@
package theme
// DefaultThemeYAML is the initial theme created at <client app dir>/theme.yaml.
//
// Palettes:
// - default: neutral base
// - primary/secondary: accent colors
// - success/warning/danger: semantic colors
//
// Each palette has a "default" value and a set of numeric modifiers:
// 50, 100, 200, 300, 400, 500, 600, 700, 800, 900.
const DefaultThemeYAML = `# Sliver client console theme.
# Colors are hex strings (e.g. "#006FEE").
primary:
default: "#006FEE"
mods:
50: "#001731"
100: "#002e62"
200: "#004493"
300: "#005bc4"
400: "#006FEE"
500: "#338ef7"
600: "#66aaf9"
700: "#99c7fb"
800: "#cce3fd"
900: "#e6f1fe"
secondary:
default: "#9353d3"
mods:
50: "#180828"
100: "#301050"
200: "#481878"
300: "#6020a0"
400: "#7828c8"
500: "#9353d3"
600: "#ae7ede"
700: "#c9a9e9"
800: "#e4d4f4"
900: "#f2eafa"
default:
default: "#3f3f46"
mods:
50: "#18181b"
100: "#27272a"
200: "#3f3f46"
300: "#52525b"
400: "#71717a"
500: "#a1a1aa"
600: "#d4d4d8"
700: "#e4e4e7"
800: "#f4f4f5"
900: "#fafafa"
success:
default: "#17c964"
mods:
50: "#052814"
100: "#095028"
200: "#0e793c"
300: "#12a150"
400: "#17c964"
500: "#45d483"
600: "#74dfa2"
700: "#a2e9c1"
800: "#d1f4e0"
900: "#e8faf0"
warning:
default: "#f5a524"
mods:
50: "#312107"
100: "#62420e"
200: "#936316"
300: "#c4841d"
400: "#f5a524"
500: "#f7b750"
600: "#f9c97c"
700: "#fbdba7"
800: "#fdedd3"
900: "#fefce8"
danger:
default: "#f31260"
mods:
50: "#310413"
100: "#610726"
200: "#920b3a"
300: "#c20e4d"
400: "#f31260"
500: "#f54180"
600: "#f871a0"
700: "#faa0bf"
800: "#fdd0df"
900: "#fee7ef"
`
+67
View File
@@ -0,0 +1,67 @@
package theme
import (
"github.com/charmbracelet/huh"
"github.com/charmbracelet/lipgloss"
)
// HuhTheme returns a huh Theme derived from the current Sliver client theme.
//
// This is used for all interactive TUI forms in the client so that colors are
// consistent with the rest of the console styling (theme.yaml).
func HuhTheme() *huh.Theme {
t := huh.ThemeBase()
border := DefaultMod(300)
text := DefaultMod(900)
dim := DefaultMod(500)
faint := DefaultMod(400)
primary := Primary()
accent := Secondary()
success := Success()
danger := Danger()
// Focused styles.
t.Focused.Base = t.Focused.Base.BorderForeground(border)
t.Focused.Card = t.Focused.Base
t.Focused.Title = t.Focused.Title.Foreground(primary).Bold(true)
t.Focused.NoteTitle = t.Focused.NoteTitle.Foreground(primary).Bold(true).MarginBottom(1)
t.Focused.Description = t.Focused.Description.Foreground(dim)
t.Focused.Directory = t.Focused.Directory.Foreground(primary)
t.Focused.File = t.Focused.File.Foreground(text)
t.Focused.ErrorIndicator = t.Focused.ErrorIndicator.Foreground(danger)
t.Focused.ErrorMessage = t.Focused.ErrorMessage.Foreground(danger)
t.Focused.SelectSelector = t.Focused.SelectSelector.Foreground(accent)
t.Focused.NextIndicator = t.Focused.NextIndicator.Foreground(accent)
t.Focused.PrevIndicator = t.Focused.PrevIndicator.Foreground(accent)
t.Focused.Option = t.Focused.Option.Foreground(text)
t.Focused.MultiSelectSelector = t.Focused.MultiSelectSelector.Foreground(accent)
t.Focused.SelectedOption = t.Focused.SelectedOption.Foreground(success)
t.Focused.SelectedPrefix = t.Focused.SelectedPrefix.Foreground(success)
t.Focused.UnselectedOption = t.Focused.UnselectedOption.Foreground(text)
t.Focused.UnselectedPrefix = t.Focused.UnselectedPrefix.Foreground(faint)
t.Focused.FocusedButton = t.Focused.FocusedButton.Foreground(text).Background(primary)
t.Focused.Next = t.Focused.FocusedButton
t.Focused.BlurredButton = t.Focused.BlurredButton.Foreground(text).Background(lipgloss.Color(DefaultMod(100)))
t.Focused.TextInput.Cursor = t.Focused.TextInput.Cursor.Foreground(primary)
t.Focused.TextInput.Placeholder = t.Focused.TextInput.Placeholder.Foreground(faint)
t.Focused.TextInput.Prompt = t.Focused.TextInput.Prompt.Foreground(accent)
// Blurred styles (same palette, but without the focused border/indicators).
t.Blurred = t.Focused
t.Blurred.Base = t.Blurred.Base.BorderStyle(lipgloss.HiddenBorder())
t.Blurred.Card = t.Blurred.Base
t.Blurred.MultiSelectSelector = lipgloss.NewStyle().SetString(" ")
t.Blurred.NextIndicator = lipgloss.NewStyle()
t.Blurred.PrevIndicator = lipgloss.NewStyle()
// Group styles.
t.Group.Title = t.Focused.Title
t.Group.Description = t.Focused.Description
return t
}
+173
View File
@@ -0,0 +1,173 @@
package theme
import (
"errors"
"os"
"path/filepath"
"sync"
"github.com/bishopfox/sliver/client/assets"
"github.com/charmbracelet/lipgloss"
"gopkg.in/yaml.v3"
)
const (
ThemeFileName = "theme.yaml"
)
type Palette struct {
Default string `yaml:"default"`
Mods map[int]string `yaml:"mods"`
}
type Theme struct {
Primary Palette `yaml:"primary"`
Secondary Palette `yaml:"secondary"`
Default Palette `yaml:"default"`
Success Palette `yaml:"success"`
Warning Palette `yaml:"warning"`
Danger Palette `yaml:"danger"`
}
var (
mu sync.RWMutex
current Theme
)
func init() {
// Ensure there is always a usable theme without I/O.
t, err := parseThemeYAML([]byte(DefaultThemeYAML))
if err == nil {
current = t
} else {
// Minimal fallback; should never happen unless DefaultThemeYAML is corrupted.
current = Theme{
Primary: Palette{Default: "#006FEE", Mods: map[int]string{}},
Default: Palette{Default: "#ffffff", Mods: map[int]string{}},
}
}
}
func ThemePath() string {
return filepath.Join(assets.GetRootAppDir(), ThemeFileName)
}
func SetCurrentTheme(t Theme) {
mu.Lock()
current = t
mu.Unlock()
}
func EnsureThemeFile() error {
p := ThemePath()
if _, err := os.Stat(p); err == nil {
return nil
} else if !errors.Is(err, os.ErrNotExist) {
return err
}
return os.WriteFile(p, []byte(DefaultThemeYAML), 0o600)
}
func LoadThemeFromDisk() (Theme, error) {
data, err := os.ReadFile(ThemePath())
if err != nil {
return Theme{}, err
}
return parseThemeYAML(data)
}
func LoadAndSetCurrentTheme() error {
_ = EnsureThemeFile()
t, err := LoadThemeFromDisk()
if err != nil {
return err
}
SetCurrentTheme(t)
return nil
}
func parseThemeYAML(data []byte) (Theme, error) {
var t Theme
if err := yaml.Unmarshal(data, &t); err != nil {
return Theme{}, err
}
normalizePalette(&t.Primary)
normalizePalette(&t.Secondary)
normalizePalette(&t.Default)
normalizePalette(&t.Success)
normalizePalette(&t.Warning)
normalizePalette(&t.Danger)
return t, nil
}
func normalizePalette(p *Palette) {
if p.Mods == nil {
p.Mods = map[int]string{}
}
}
func (t Theme) paletteColor(p Palette) lipgloss.Color {
if p.Default != "" {
return lipgloss.Color(p.Default)
}
// Defensive fallback.
return lipgloss.Color("#ffffff")
}
func (t Theme) paletteMod(p Palette, mod int) lipgloss.Color {
if mod != 0 {
if v, ok := p.Mods[mod]; ok && v != "" {
return lipgloss.Color(v)
}
}
return t.paletteColor(p)
}
// Primary returns the theme primary color.
func Primary() lipgloss.Color { return Current().paletteColor(Current().Primary) }
func PrimaryMod(mod int) lipgloss.Color {
t := Current()
return t.paletteMod(t.Primary, mod)
}
// Secondary returns the theme secondary color.
func Secondary() lipgloss.Color { return Current().paletteColor(Current().Secondary) }
func SecondaryMod(mod int) lipgloss.Color {
t := Current()
return t.paletteMod(t.Secondary, mod)
}
// Default returns the theme default/neutral color.
func Default() lipgloss.Color { return Current().paletteColor(Current().Default) }
func DefaultMod(mod int) lipgloss.Color {
t := Current()
return t.paletteMod(t.Default, mod)
}
// Success returns the theme success color.
func Success() lipgloss.Color { return Current().paletteColor(Current().Success) }
func SuccessMod(mod int) lipgloss.Color {
t := Current()
return t.paletteMod(t.Success, mod)
}
// Warning returns the theme warning color.
func Warning() lipgloss.Color { return Current().paletteColor(Current().Warning) }
func WarningMod(mod int) lipgloss.Color {
t := Current()
return t.paletteMod(t.Warning, mod)
}
// Danger returns the theme danger color.
func Danger() lipgloss.Color { return Current().paletteColor(Current().Danger) }
func DangerMod(mod int) lipgloss.Color {
t := Current()
return t.paletteMod(t.Danger, mod)
}
// Current returns the in-memory current theme (defaults to DefaultThemeYAML).
func Current() Theme {
mu.RLock()
defer mu.RUnlock()
return current
}
-3
View File
@@ -31,9 +31,6 @@ import (
)
const (
normal = "\033[0m"
bold = "\033[1m"
defaultVersion = "devel"
defaultReleasesURL = "https://api.github.com/repos/BishopFox/sliver/releases"
)
-3
View File
@@ -1,3 +0,0 @@
{
"extends": "next/core-web-vitals"
}
-2
View File
@@ -25,7 +25,6 @@ import AsciinemaPlayer from "./asciinema";
import Youtube from "./youtube";
export type MarkdownProps = {
key?: string;
markdown: string;
};
@@ -252,7 +251,6 @@ const MarkdownViewer = (props: MarkdownProps) => {
<div className="relative">
<div className={proseClassName}>
<Markdown
key={props.key || `${Math.random()}`}
remarkPlugins={[remarkGfm]}
components={{
...headingComponents,
+21
View File
@@ -133,6 +133,17 @@ export default function TopNavbar(props: TopNavbarProps) {
</Button>
</NavbarItem>
<NavbarItem isActive={router.pathname.startsWith("/talks")}>
<Button
variant="light"
color={router.pathname === "/talks" ? "primary" : "default"}
as={Link}
onPress={() => router.push("/talks")}
>
Talks
</Button>
</NavbarItem>
<NavbarItem isActive={router.pathname.startsWith("/docs")}>
<Button
variant="light"
@@ -212,6 +223,16 @@ export default function TopNavbar(props: TopNavbarProps) {
>
Tutorials
</Button>
<Button
variant={router.pathname.startsWith("/talks") ? "solid" : "light"}
color={router.pathname.startsWith("/talks") ? "primary" : "default"}
onPress={() => {
router.push("/talks");
setIsMobileMenuOpen(false);
}}
>
Talks
</Button>
<Button
variant={router.pathname.startsWith("/docs") ? "solid" : "light"}
color={router.pathname.startsWith("/docs") ? "primary" : "default"}
+226 -5
View File
@@ -1,19 +1,240 @@
import React from "react";
type YoutubePlayerState = {
PLAYING: number;
};
type YoutubePlayerStateChangeEvent = {
data: number;
};
type YoutubePlayer = {
destroy?: () => void;
};
type YoutubePlayerCtor = new (
element: HTMLIFrameElement | string,
options?: {
events?: {
onStateChange?: (event: YoutubePlayerStateChangeEvent) => void;
};
}
) => YoutubePlayer;
type YoutubeNamespace = {
Player: YoutubePlayerCtor;
PlayerState: YoutubePlayerState;
};
declare global {
interface Window {
YT?: YoutubeNamespace;
onYouTubeIframeAPIReady?: () => void;
__sliverYoutubeApiPromise?: Promise<YoutubeNamespace>;
}
}
export type YoutubeProps = {
className?: string;
iframeClassName?: string;
width?: number;
height?: number;
responsive?: boolean;
title?: string;
startAt?: number;
embedId: string;
embedId?: string;
url?: string;
onInteract?: () => void;
onPlay?: () => void;
};
const YOUTUBE_ID_PATTERN = /^[A-Za-z0-9_-]{11}$/;
function parseYoutubeEmbedIdFromUrl(input: string): string | null {
try {
const parsed = new URL(input);
const host = parsed.hostname.toLowerCase().replace(/^www\./, "");
const parts = parsed.pathname.split("/").filter(Boolean);
let candidate = "";
if (host === "youtu.be" && parts.length > 0) {
candidate = parts[0] || "";
} else if (host === "youtube.com" || host === "m.youtube.com") {
if (parts[0] === "watch") {
candidate = parsed.searchParams.get("v") || "";
} else if ((parts[0] === "embed" || parts[0] === "shorts") && parts.length > 1) {
candidate = parts[1] || "";
}
}
return YOUTUBE_ID_PATTERN.test(candidate) ? candidate : null;
} catch {
return null;
}
}
function resolveYoutubeEmbedId(embedId?: string, url?: string): string | null {
const idCandidate = (embedId || "").trim();
if (YOUTUBE_ID_PATTERN.test(idCandidate)) {
return idCandidate;
}
if (idCandidate) {
const parsedFromEmbedId = parseYoutubeEmbedIdFromUrl(idCandidate);
if (parsedFromEmbedId) {
return parsedFromEmbedId;
}
}
const urlCandidate = (url || "").trim();
if (urlCandidate) {
return parseYoutubeEmbedIdFromUrl(urlCandidate);
}
return null;
}
function loadYoutubeIframeApi(): Promise<YoutubeNamespace> {
if (typeof window === "undefined") {
return Promise.reject(new Error("YouTube API cannot load during SSR"));
}
if (window.YT?.Player) {
return Promise.resolve(window.YT);
}
if (window.__sliverYoutubeApiPromise) {
return window.__sliverYoutubeApiPromise;
}
window.__sliverYoutubeApiPromise = new Promise<YoutubeNamespace>((resolve, reject) => {
const previousReadyHandler = window.onYouTubeIframeAPIReady;
window.onYouTubeIframeAPIReady = () => {
previousReadyHandler?.();
if (window.YT?.Player) {
resolve(window.YT);
} else {
reject(new Error("YouTube API loaded without Player support"));
}
};
const existingScript = document.querySelector<HTMLScriptElement>(
"script[data-youtube-iframe-api='true']"
);
if (existingScript) {
return;
}
const script = document.createElement("script");
script.src = "https://www.youtube.com/iframe_api";
script.async = true;
script.dataset.youtubeIframeApi = "true";
script.onerror = () => reject(new Error("Failed to load YouTube iframe API"));
document.head.appendChild(script);
});
return window.__sliverYoutubeApiPromise;
}
export default function Youtube(props: YoutubeProps) {
const {
className,
iframeClassName,
width,
height,
responsive,
title,
startAt,
embedId: embedIdProp,
url,
onInteract,
onPlay,
} = props;
const embedId = resolveYoutubeEmbedId(embedIdProp, url);
const iframeRef = React.useRef<HTMLIFrameElement | null>(null);
const onPlayRef = React.useRef(onPlay);
React.useEffect(() => {
onPlayRef.current = onPlay;
}, [onPlay]);
React.useEffect(() => {
if (!embedId || !onPlayRef.current || !iframeRef.current) {
return;
}
let player: YoutubePlayer | null = null;
let disposed = false;
loadYoutubeIframeApi()
.then((YT) => {
if (disposed || !iframeRef.current) {
return;
}
player = new YT.Player(iframeRef.current, {
events: {
onStateChange: (event) => {
if (event.data === YT.PlayerState.PLAYING) {
onPlayRef.current?.();
}
},
},
});
})
.catch(() => {
// No-op: if API fails, videos still load via standard iframe behavior.
});
return () => {
disposed = true;
player?.destroy?.();
};
}, [embedId]);
if (!embedId) {
return null;
}
const startAtParam =
Number.isFinite(startAt) && startAt && startAt > 0
? `&start=${Math.floor(startAt)}`
: "";
const enableJsApi = onPlay ? "&enablejsapi=1" : "";
const src = `https://www.youtube-nocookie.com/embed/${embedId}?rel=0${startAtParam}${enableJsApi}`;
const playerTitle = title || "YouTube video player";
const isResponsive = responsive ?? true;
const handleInteract = () => onInteract?.();
if (isResponsive) {
return (
<div className={className} onPointerDown={handleInteract}>
<div className="relative w-full overflow-hidden rounded-xl bg-black pb-[56.25%]">
<iframe
ref={iframeRef}
className={`absolute left-0 top-0 h-full w-full ${iframeClassName || ""}`.trim()}
src={src}
title={playerTitle}
loading="lazy"
referrerPolicy="strict-origin-when-cross-origin"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowFullScreen
/>
</div>
</div>
);
}
return (
<div className={props.className}>
<div className={className} onPointerDown={handleInteract}>
<iframe
width={`${props.width ? props.width : 640}`}
height={`${props.height ? props.height : 360}`}
src={`https://youtu.be/embed/${props.embedId}`}
ref={iframeRef}
width={`${width ? width : 640}`}
height={`${height ? height : 360}`}
className={iframeClassName}
src={src}
title={playerTitle}
loading="lazy"
referrerPolicy="strict-origin-when-cross-origin"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowFullScreen
/>
+11
View File
@@ -0,0 +1,11 @@
import nextCoreWebVitals from "eslint-config-next/core-web-vitals";
const config = [
...nextCoreWebVitals,
{
// Monaco bundles are vendor artifacts, not authored source files.
ignores: ["public/js/**"],
},
];
export default config;
+1 -16
View File
@@ -1,6 +1,3 @@
const { spawnSync } = require('child_process');
const path = require('path');
/** @type {import('next').NextConfig} */
const nextConfig = {
output: 'export',
@@ -11,18 +8,6 @@ const nextConfig = {
experimental: {
scrollRestoration: true,
},
webpack: (config, { isServer }) => {
if (isServer) {
const result = spawnSync(process.execPath, [path.join(__dirname, 'prebuild/run-prebuild.js')], {
stdio: 'inherit',
});
if (result.status !== 0) {
throw new Error('[prebuild] webpack hook failed');
}
}
return config;
}
}
};
module.exports = nextConfig;
+2485 -1025
View File
File diff suppressed because it is too large. Load diff
+5 -5
View File
@@ -3,10 +3,10 @@
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "npm run prebuild && concurrently -k -n prebuild,dev \"npm run prebuild:watch -- --skip-initial\" \"next dev\"",
"build": "npm run prebuild && next build",
"dev": "npm run prebuild && concurrently -k -n prebuild,dev \"npm run prebuild:watch -- --skip-initial\" \"next dev --webpack\"",
"build": "npm run prebuild && next build --webpack",
"start": "next start",
"lint": "next lint",
"lint": "eslint .",
"prebuild": "node ./prebuild/run-prebuild.js",
"prebuild:watch": "node ./prebuild/watch.js",
"offline": "npm run build && zip -r www.zip ./out",
@@ -26,7 +26,7 @@
"framer-motion": "^12.23.22",
"fuse.js": "^7.1.0",
"lunr": "^2.3.9",
"next": "^15.5.9",
"next": "^16.1.6",
"next-themes": "^0.4.6",
"react": "^19.2.0",
"react-dom": "^19.2.0",
@@ -44,7 +44,7 @@
"autoprefixer": "^10.4.21",
"concurrently": "^9.1.0",
"eslint": "^9.37.0",
"eslint-config-next": "^15.5.4",
"eslint-config-next": "^16.1.6",
"postcss": "^8.5.6",
"tailwindcss": "^4.1.14",
"typescript": "^5.9.3"
+1 -1
View File
@@ -2,7 +2,7 @@ import Document, { Head, Html, Main, NextScript } from "next/document";
import React from "react";
// This generates the https: and wss: "connect-src" directives based on the above backends list so its a little easier to edit.
const CSP = `default-src 'none'; script-src 'self' 'unsafe-eval'; img-src 'self'; style-src 'self' 'unsafe-inline'; font-src 'self'; frame-src https://youtu.be; connect-src 'self'`;
const CSP = `default-src 'none'; script-src 'self' 'unsafe-eval' https://www.youtube.com https://s.ytimg.com; img-src 'self' data: https://i.ytimg.com https://*.ytimg.com; style-src 'self' 'unsafe-inline'; font-src 'self'; frame-src https://youtu.be https://www.youtube.com https://youtube.com https://www.youtube-nocookie.com; connect-src 'self'`;
class MyDocument extends Document {
static async getInitialProps(ctx: any) {
+2 -2
View File
@@ -127,7 +127,7 @@ const DocsIndexPage: NextPage = () => {
</div>
<div className="grid grid-cols-1 gap-6 lg:grid-cols-12 lg:gap-8">
<aside className="hidden lg:block lg:col-span-3">
<div className="sticky top-24 ml-4 flex flex-col gap-3">
<div className="sticky top-16 ml-4 flex flex-col gap-3">
<Input
className="mt-2"
placeholder="Filter..."
@@ -171,7 +171,7 @@ const DocsIndexPage: NextPage = () => {
<Divider />
<CardBody>
<MarkdownViewer
key={name || `${Math.random()}`}
key={name}
markdown={markdown || ""}
/>
</CardBody>
@@ -1,6 +1,4 @@
You'll want to compile from a MacOS or Linux machine. Compiling from native Windows is possible in theory, but none of the asset scripts are designed to run on Windows. You can cross-compile the Windows server/client binaries from a better operating system like Linux or MacOS. If you only have a Windows machine, see "Windows Builds" below (TL;DR: use WSL).
# Sliver v1.6.x
You'll want to compile Sliver from a MacOS or Linux machine. Compiling from native Windows is possible in theory, but none of the scripts are designed to run on Windows. You can cross-compile the Windows server/client binaries from a better operating systems like Linux or MacOS. If you only have a Windows machine, you should be able to use the Windows Subsystem for Linux (WSL).
To compile from source you'll need:
@@ -62,7 +60,7 @@ The Docker build includes mingw and Metasploit, so it can take a while to build
### Windows Builds
If all you have is a Windows machine, the easiest way to build Sliver is using [WSL](https://docs.microsoft.com/en-us/windows/wsl/install-win10) and following the Linux/cross-compile instructions above. To cross-compile a native Windows binary use `make windows-amd64` and copy it to your Windows file system (i.e. `/mnt/c/Users/foo/Desktop`) and run it using a terminal that supports ANSI sequences such as the [Windows Terminal](https://github.com/microsoft/terminal).
If all you have is a Windows machine, the easiest way to build Sliver is using [WSL](https://docs.microsoft.com/en-us/windows/wsl/install-win10) and following the Linux/cross-compile instructions above. To cross-compile a Windows binary use `make windows-amd64` and copy it to your Windows file system (i.e. `/mnt/c/Users/foo/Desktop`) and, you will need to run it using a terminal that supports ANSI sequences such as the [Windows Terminal](https://github.com/microsoft/terminal).
# Developers
@@ -1,14 +1,14 @@
**NOTE:** Any platform can cross-compile a standalone executable to any other platform out of the box; you primarily need cross-compilers when using CGO-based formats like `--format shared`, and for `--format shellcode` on macOS/Linux.
Any platform can cross-compile a standalone executable to any other platform out of the box; you primarily need cross-compilers when using CGO-based formats like `--format shared`, and for `--format shellcode` on macOS/Linux.
Sliver can tell you which platforms it can likely target based on the server's platform and available cross-compilers by running the `generate info` command in the console.
Sliver supports [External Builders](/docs?name=External+Builders), which can be used to easily cross-compile implants.
## From Linux to MacOS/Windows
## From Linux to MacOS
Sliver embeds a Zig cross-compiler out of the box, so Windows shared library and shellcode implants from Linux do not require mingw-w64.
To compile MacOS shared library implants from Linux, we recommend using https://github.com/tpoechtrager/osxcross by default Sliver will look in `/opt/osxcross` but you can override this via [environment variables](/docs?name=Environment+Variables). If you do not have a MacOS-based machine you can use GitHub Actions' MacOS instances to build OSXCross.
To compile MacOS shared library or shellcode payloads from Linux, we recommend using [OSX Cross](https://github.com/tpoechtrager/osxcross) by default Sliver will look in `/opt/osxcross` but you can override this via [environment variables](/docs?name=Environment+Variables). If you do not have a MacOS-based machine you can use GitHub Actions' MacOS instances to build OSXCross.
**NOTE:** Sliver expects the root of the osxcross git repo to be located at `/opt/osxcross` and the actual binaries in `/opt/osxcross/target/bin`.
@@ -32,8 +32,6 @@ Any `sliver-server` binary can be started as a builder process using [operator c
When started as a builder, the Sliver process will mirror log output to stdout by default, however this can be disabled (see `sliver-server builder --help`).
**⚠️ IMPORTANT:** Make sure the builder and server have identical `http-c2.json` configuration files to avoid incompatibility problems.
**⚠️ IMPORTANT:** Builders must have unique names, by default the builder's hostname will be used, but this can be changed using the `--name` cli flag.
#### External Builds
@@ -0,0 +1,81 @@
This page summarizes implant payload format compatibility by target OS/architecture (`GOOS/GOARCH`).
## Common Platforms
| Target OS/Arch | Executable | Shared Library | Shellcode | Service |
| --------------- | ---------- | -------------- | --------- | ------- |
| `windows/386` | ✅ | ✅ | ✅ | ✅ |
| `windows/amd64` | ✅ | ✅ | ✅ | ✅ |
| `windows/arm64` | ⚠️ | ⚠️ | ❌ | ⚠️ |
| `linux/386` | ✅ | ✅ | ❌ | N/A |
| `linux/amd64` | ✅ | ✅ | ✅ | N/A |
| `linux/arm64` | ✅ | ✅ | ✅ | N/A |
| `darwin/amd64` | ✅ | ✅ | ❌ | N/A |
| `darwin/arm64` | ✅ | ✅ | ✅ | N/A |
### Important Notes
- `✅` = first-class support in Sliver's built-in target matrix.
- `⚠️` = generic/experimental target (not first-class; may fail depending on toolchain/target).
- `❌` = not currently supported for that payload format.
- `Service` is a Windows-only format.
- `Shellcode` is currently supported for `windows/{386,amd64}`, `linux/{amd64,arm64}`, and `darwin/arm64`.
- `Shellcode` and `Shared Library` for MacOS may require a [cross-compiler](/docs?name=Cross-compiling+Implants) or an [external builder](/docs?name=External+Builders) depending on your platform.
- On macOS hosts, targeting `linux/386` for shared library/shellcode builds is currently not reliable.
Use `generate info` in the Sliver console to see what your current server can build with its configured toolchains.
## All GOOS/GOARCH Targets
The table below covers all `GOOS/GOARCH` entries from `go tool dist list` (Go 1.24+), including common platforms:
| Target GOOS/GOARCH | Common Platform | Executable | Shared Library | Shellcode | Service |
| ------------------ | --------------- | ---------- | -------------- | --------- | ------- |
| `aix/ppc64` | No | ⚠️ | ❌ | ❌ | N/A |
| `android/386` | No | ⚠️ | ❌ | ❌ | N/A |
| `android/amd64` | No | ⚠️ | ❌ | ❌ | N/A |
| `android/arm` | No | ⚠️ | ❌ | ❌ | N/A |
| `android/arm64` | No | ⚠️ | ❌ | ❌ | N/A |
| `darwin/amd64` | Yes | ✅ | ✅ | ❌ | N/A |
| `darwin/arm64` | Yes | ✅ | ✅ | ✅ | N/A |
| `dragonfly/amd64` | No | ⚠️ | ❌ | ❌ | N/A |
| `freebsd/386` | No | ⚠️ | ❌ | ❌ | N/A |
| `freebsd/amd64` | No | ⚠️ | ❌ | ❌ | N/A |
| `freebsd/arm` | No | ⚠️ | ❌ | ❌ | N/A |
| `freebsd/arm64` | No | ⚠️ | ❌ | ❌ | N/A |
| `freebsd/riscv64` | No | ⚠️ | ❌ | ❌ | N/A |
| `illumos/amd64` | No | ⚠️ | ❌ | ❌ | N/A |
| `ios/amd64` | No | ⚠️ | ❌ | ❌ | N/A |
| `ios/arm64` | No | ⚠️ | ❌ | ❌ | N/A |
| `js/wasm` | No | ⚠️ | ❌ | ❌ | N/A |
| `linux/386` | Yes | ✅ | ✅ | ❌ | N/A |
| `linux/amd64` | Yes | ✅ | ✅ | ✅ | N/A |
| `linux/arm` | No | ⚠️ | ❌ | ❌ | N/A |
| `linux/arm64` | Yes | ✅ | ✅ | ✅ | N/A |
| `linux/loong64` | No | ⚠️ | ❌ | ❌ | N/A |
| `linux/mips` | No | ⚠️ | ❌ | ❌ | N/A |
| `linux/mips64` | No | ⚠️ | ❌ | ❌ | N/A |
| `linux/mips64le` | No | ⚠️ | ❌ | ❌ | N/A |
| `linux/mipsle` | No | ⚠️ | ❌ | ❌ | N/A |
| `linux/ppc64` | No | ⚠️ | ❌ | ❌ | N/A |
| `linux/ppc64le` | No | ⚠️ | ❌ | ❌ | N/A |
| `linux/riscv64` | No | ⚠️ | ❌ | ❌ | N/A |
| `linux/s390x` | No | ⚠️ | ❌ | ❌ | N/A |
| `netbsd/386` | No | ⚠️ | ❌ | ❌ | N/A |
| `netbsd/amd64` | No | ⚠️ | ❌ | ❌ | N/A |
| `netbsd/arm` | No | ⚠️ | ❌ | ❌ | N/A |
| `netbsd/arm64` | No | ⚠️ | ❌ | ❌ | N/A |
| `openbsd/386` | No | ⚠️ | ❌ | ❌ | N/A |
| `openbsd/amd64` | No | ⚠️ | ❌ | ❌ | N/A |
| `openbsd/arm` | No | ⚠️ | ❌ | ❌ | N/A |
| `openbsd/arm64` | No | ⚠️ | ❌ | ❌ | N/A |
| `openbsd/ppc64` | No | ⚠️ | ❌ | ❌ | N/A |
| `openbsd/riscv64` | No | ⚠️ | ❌ | ❌ | N/A |
| `plan9/386` | No | ⚠️ | ❌ | ❌ | N/A |
| `plan9/amd64` | No | ⚠️ | ❌ | ❌ | N/A |
| `plan9/arm` | No | ⚠️ | ❌ | ❌ | N/A |
| `solaris/amd64` | No | ⚠️ | ❌ | ❌ | N/A |
| `wasip1/wasm` | No | ⚠️ | ❌ | ❌ | N/A |
| `windows/386` | Yes | ✅ | ✅ | ✅ | ✅ |
| `windows/amd64` | Yes | ✅ | ✅ | ✅ | ✅ |
| `windows/arm64` | Yes | ⚠️ | ⚠️ | ❌ | ⚠️ |
+133
View File
@@ -0,0 +1,133 @@
### Console Themes
Sliver’s interactive consoles (both `sliver-client` and the server-only console) use a theme file to drive all lipgloss styling.
- Theme path: `~/.sliver-client/theme.yaml`
- Settings path: `~/.sliver-client/tui-settings.yaml`
If `theme.yaml` is missing, Sliver will create it with the default palette.
#### Palette Structure
`theme.yaml` defines six palettes:
- `primary`
- `secondary`
- `default`
- `success`
- `warning`
- `danger`
Each palette has a base `default` color and a set of modifier colors (`50..900`):
```yaml
primary:
default: "#006FEE"
mods:
50: "#001731"
100: "#002e62"
200: "#004493"
300: "#005bc4"
400: "#006FEE"
500: "#338ef7"
600: "#66aaf9"
700: "#99c7fb"
800: "#cce3fd"
900: "#e6f1fe"
```
### Prompt Settings
The console prompt is configurable via `tui-settings.yaml`:
```yaml
prompt: "host"
prompt_template: |
{{- if .IsServer -}}
{{ .Styles.Bold.Render "[server]" }} {{ .Styles.Underline.Render "sliver" }}{{ .Target.Suffix }} >
{{- else -}}
{{- if .Host -}}{{ .Styles.BoldPrimary.Render (printf "[%s]" .Host) }} {{- end -}}
{{ .Styles.Underline.Render "sliver" }}{{ .Target.Suffix }} >
{{- end -}}
```
#### `prompt` Modes
- `operator-host`
- `sliver-client`: `[operator@host] sliver >`
- server console: `[server] sliver >`
- `host`
- `sliver-client`: `[host] sliver >`
- server console: `[server] sliver >`
- `basic`
- both: `sliver >`
- `custom`
- the entire prompt text is rendered from `prompt_template` (Go `text/template`)
- if rendering fails, Sliver falls back to a minimal prompt: ` > `
### Custom Prompt Templates
When `prompt: custom`, Sliver evaluates `prompt_template` as a Go `text/template` every time the prompt is displayed.
#### Template Variables
Top-level values:
- `.IsServer` (bool)
- `.Connected` (bool)
- `.Operator` (string)
- `.Host` (string)
- `.Port` (int)
- `.HostPort` (string, `host:port` when available)
- `.Now` (time)
Active target:
- `.Target.SessionName` (string)
- `.Target.BeaconName` (string)
- `.Target.Suffix` (string)
- pre-rendered themed suffix matching the default prompt, like ` (my-session)` when a session or beacon is selected
Theme colors:
- `.Colors.Primary.Default`
- `.Colors.Primary.Mods` (map; use `index`, e.g. `index .Colors.Primary.Mods 500`)
- same structure for `.Colors.Secondary`, `.Colors.Default`, `.Colors.Success`, `.Colors.Warning`, `.Colors.Danger`
Theme styles (lipgloss styles already wired to `theme.yaml`):
- `.Styles.Bold`, `.Styles.Underline`
- `.Styles.Primary`, `.Styles.Secondary`, `.Styles.Default`, `.Styles.Success`, `.Styles.Warning`, `.Styles.Danger`
- `.Styles.BoldPrimary`, `.Styles.BoldSecondary`, `.Styles.BoldDefault`, `.Styles.BoldSuccess`, `.Styles.BoldWarning`, `.Styles.BoldDanger`
- legacy names: `.Styles.Red`, `.Styles.Green`, `.Styles.Orange`, `.Styles.Blue`, `.Styles.Purple`, `.Styles.Cyan`, `.Styles.Gray`, `.Styles.Black`
#### Examples
Show user and host (client), show server tag (server console):
```gotemplate
{{- if .IsServer -}}
{{ .Styles.Bold.Render "[server]" }} {{ .Styles.Underline.Render "sliver" }}{{ .Target.Suffix }} >
{{- else -}}
{{- if and .Operator .Host -}}
{{ .Styles.BoldPrimary.Render (printf "[%s@%s]" .Operator .Host) }}
{{- else if .Host -}}
{{ .Styles.BoldPrimary.Render (printf "[%s]" .Host) }}
{{- end -}}
{{ .Styles.Underline.Render "sliver" }}{{ .Target.Suffix }} >
{{- end -}}
```
Basic prompt everywhere:
```gotemplate
{{ .Styles.Underline.Render "sliver" }}{{ .Target.Suffix }} >
```
Use a lighter primary modifier for the host tag:
```gotemplate
{{- $c := index .Colors.Primary.Mods 500 -}}
{{- if .Host -}}{{ (.Styles.Bold.Foreground $c).Render (printf "[%s]" .Host) }} {{- end -}}
{{ .Styles.Underline.Render "sliver" }}{{ .Target.Suffix }} >
```
+139
View File
@@ -0,0 +1,139 @@
import Youtube from "@/components/youtube";
import { faChevronRight } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Button, Card, CardFooter, CardHeader } from "@heroui/react";
import { NextPage } from "next";
import Head from "next/head";
import React from "react";
type Talk = {
title: string;
description: string;
url: string;
};
type TalkSection = {
title: "Workshops" | "General Tradecraft";
description: string;
badge: string;
talks: Talk[];
};
const workshopTalks: Talk[] = [
{
title:"Getting Started with Sliver v1.6",
description: "Introductory workshop covering basics and new features.",
url: "https://www.youtube.com/watch?v=IOiyXYp1lDc",
},
{
title: "Building Traffic Encoders",
description: "From concept to practical encoder strategy in Sliver.",
url: "https://www.youtube.com/watch?v=6unwFhurm-E",
},
{
title: "Sliver Staging and Automation",
description: "Workflow patterns for payload staging and repeatable ops.",
url: "https://www.youtube.com/watch?v=vuQ5tG5kelI&feature=youtu.be",
},
];
const generalTradecraftTalks: Talk[] = [
{
title: "Offensive WASM",
description: "Applying WebAssembly techniques in offensive tradecraft.",
url: "https://www.youtube.com/watch?v=RnSLsnP4imQ",
},
{
title: "The Sliver C2 Framework",
description: "General discussion of C2 design.",
url: "https://www.youtube.com/watch?v=tkjMZuZ_8nw",
}
];
const talkSections: TalkSection[] = [
{
title: "Workshops",
description: "Hands-on workshop recordings focused on Sliver workflows.",
badge: "Workshop",
talks: workshopTalks,
},
{
title: "General Tradecraft",
description: "Broader offensive engineering and tradecraft talks.",
badge: "Tradecraft",
talks: generalTradecraftTalks,
},
];
const TalksIndexPage: NextPage = () => {
const [footerHidden, setFooterHidden] = React.useState<Record<string, boolean>>(
{}
);
const hideFooterFor = React.useCallback((url: string) => {
setFooterHidden((prev) => (prev[url] ? prev : { ...prev, [url]: true }));
}, []);
return (
<>
<Head>
<title>Sliver Talks</title>
</Head>
<div className="mt-6 px-4 pb-8 sm:px-6 lg:px-12">
{talkSections.map((section, sectionIndex) => {
return (
<section
key={section.title}
className={sectionIndex === 0 ? "" : "mt-8"}
aria-label={section.title}
>
<div className="mb-4">
<h2 className="text-3xl font-semibold">{section.title}</h2>
<p className="mt-2 text-sm text-default-500">{section.description}</p>
</div>
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-9">
{section.talks.map((talk) => (
<div key={talk.url} className="sm:col-span-1 lg:col-span-3">
<Card isFooterBlurred className="relative z-0 overflow-hidden">
<CardHeader className="absolute z-10 top-1 flex-col items-end">
</CardHeader>
<Youtube
className="w-full"
url={talk.url}
title={talk.title}
onPlay={() => hideFooterFor(talk.url)}
/>
{footerHidden[talk.url] ? null : (
<CardFooter className="absolute bottom-0 z-10 bg-black/40 border-t-1 border-default-600 dark:border-default-100">
<div className="flex w-full items-center gap-2">
<p className="text-xs text-white/80">{talk.description}</p>
<Button
variant="ghost"
color="warning"
size="sm"
className="ml-auto"
onPress={() => {
window.open(talk.url, "_blank", "noopener,noreferrer");
}}
>
Watch <FontAwesomeIcon icon={faChevronRight} />
</Button>
</div>
</CardFooter>
)}
</Card>
</div>
))}
</div>
</section>
);
})}
</div>
</>
);
};
export default TalksIndexPage;
+3 -2
View File
@@ -127,8 +127,9 @@ const TutorialsIndexPage: NextPage = () => {
</div>
<div className="grid grid-cols-1 gap-6 lg:grid-cols-12 lg:gap-8">
<aside className="hidden lg:block lg:col-span-3">
<div className="sticky top-24 ml-4 flex flex-col gap-3">
<div className="sticky top-16 ml-4 flex flex-col gap-3">
<Input
className="mt-2"
placeholder="Filter..."
startContent={<FontAwesomeIcon icon={faSearch} />}
value={filterValue}
@@ -170,7 +171,7 @@ const TutorialsIndexPage: NextPage = () => {
<Divider />
<CardBody>
<MarkdownViewer
key={name || `${Math.random()}`}
key={name}
markdown={markdown || ""}
/>
</CardBody>
+32
View File
@@ -7,6 +7,37 @@
Consolas, "Liberation Mono", "Courier New", monospace;
}
/* Keep layout width stable when page content toggles vertical scrolling. */
html,
body {
scrollbar-gutter: stable both-edges;
}
/*
Keep html as the single scrolling element. Setting overflow on both html
and body breaks sticky sidebar behavior on docs/tutorial pages.
*/
html {
overflow-y: scroll;
}
/* Fallback for engines that do not support scrollbar-gutter. */
@supports not (scrollbar-gutter: stable) {
html {
overflow-y: scroll;
}
}
/*
Desktop sidebar pages are sensitive to viewport-width changes when the page
scrollbar appears/disappears. Keep the page scrollbar track persistent.
*/
@media (min-width: 1024px) {
html {
overflow-y: scroll;
}
}
code,
pre {
font-family: var(--font-mono);
@@ -19,6 +50,7 @@ pre {
.sliver-scrollbar {
scrollbar-width: thin;
scrollbar-color: rgba(148, 163, 184, 0.7) transparent;
scrollbar-gutter: stable;
}
.sliver-scrollbar::-webkit-scrollbar {
+12 -4
View File
@@ -1,7 +1,11 @@
{
"compilerOptions": {
"target": "es2020",
"lib": ["dom", "dom.iterable", "esnext"],
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
@@ -11,10 +15,12 @@
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"jsx": "react-jsx",
"incremental": true,
"paths": {
"@/*": ["./*"]
"@/*": [
"./*"
]
}
},
"include": [
@@ -25,5 +31,7 @@
"prebuild/generate-tutorials.js",
"util/__generated__/prebuild-version.ts"
],
"exclude": ["node_modules"]
"exclude": [
"node_modules"
]
}
+6
View File
@@ -1351,6 +1351,12 @@ func reconfigureHandler(data []byte, resp RPCResponse) {
if reconfigReq.BeaconJitter != 0 {
transports.SetJitter(reconfigReq.BeaconJitter)
}
if reconfigReq.C2URI != "" {
// {{if .Config.Debug}}
log.Printf("[*] Received new C2 URI: %s", reconfigReq.C2URI)
// {{end}}
transports.SetC2URI(reconfigReq.C2URI)
}
// {{end}}
reconfigResp := &sliverpb.Reconfigure{}
@@ -125,6 +125,8 @@ func ShellReqHandler(envelope *sliverpb.Envelope, connection *transports.Connect
Type: sliverpb.MsgTunnelClose,
Data: tunnelClose,
}
systemShell.Wait()
}
for _, rc := range tunnel.Readers {
+19
View File
@@ -152,6 +152,13 @@ func beaconStartup() {
log.Printf("Next beacon = %v", beacon)
// {{end}}
if beacon != nil {
// skip stale beacons when c2-uri has changed
if c2 := transports.GetC2URI(); c2 != "" && c2 != beacon.ActiveC2 {
// {{if .Config.Debug}}
log.Printf("[beacon] skipping stale beacon for %s, c2-uri is %s", beacon.ActiveC2, c2)
// {{end}}
continue
}
err := beaconMainLoop(beacon)
if err != nil {
connectionErrors++
@@ -160,6 +167,10 @@ func beaconStartup() {
}
}
}
// skip 1m reconnect sleep if c2-uri just changed
if c2 := transports.GetC2URI(); c2 != "" && (beacon == nil || c2 != beacon.ActiveC2) {
continue
}
reconnect := transports.GetReconnectInterval()
// {{if .Config.Debug}}
log.Printf("Reconnect sleep: %s", reconnect)
@@ -287,6 +298,14 @@ func beaconMainLoop(beacon *transports.Beacon) error {
case <-shortCircuit:
// Short circuit current duration with no error
}
// check if reconfig used to set a new C2-URI
if c2 := transports.GetC2URI(); c2 != "" && c2 != beacon.ActiveC2 {
// {{if .Config.Debug}}
log.Printf("[beacon] C2 URI changed to %s, reconnecting...", c2)
// {{end}}
return nil
}
}
return nil
}
+18
View File
@@ -36,6 +36,18 @@ const (
strategySequential = "s"
)
var (
dynamicC2URI = ""
)
func SetC2URI(uri string) {
dynamicC2URI = uri
}
func GetC2URI() string {
return dynamicC2URI
}
// C2Generator - Creates a stream of C2 URLs based on a connection strategy
func C2Generator(abort <-chan struct{}, temporaryC2 ...string) <-chan *url.URL {
// {{if .Config.Debug}}
@@ -78,6 +90,12 @@ func C2Generator(abort <-chan struct{}, temporaryC2 ...string) <-chan *url.URL {
default:
next = c2Servers[c2Counter%uint(len(c2Servers))]()
}
// check if reconfig used to set a new C2-URI
if dynamic := GetC2URI(); dynamic != "" {
next = dynamic
}
c2Counter++
if ^uint(0) < c2Counter {
panic("counter overflow")
+356 -346
View File
@@ -9592,6 +9592,7 @@ type ReconfigureReq struct {
ReconnectInterval int64 `protobuf:"varint,1,opt,name=ReconnectInterval,proto3" json:"ReconnectInterval,omitempty"`
BeaconInterval int64 `protobuf:"varint,2,opt,name=BeaconInterval,proto3" json:"BeaconInterval,omitempty"`
BeaconJitter int64 `protobuf:"varint,3,opt,name=BeaconJitter,proto3" json:"BeaconJitter,omitempty"`
C2URI string `protobuf:"bytes,4,opt,name=C2URI,proto3" json:"C2URI,omitempty"`
Request *commonpb.Request `protobuf:"bytes,9,opt,name=Request,proto3" json:"Request,omitempty"`
}
@@ -9648,6 +9649,13 @@ func (x *ReconfigureReq) GetBeaconJitter() int64 {
return 0
}
func (x *ReconfigureReq) GetC2URI() string {
if x != nil {
return x.C2URI
}
return ""
}
func (x *ReconfigureReq) GetRequest() *commonpb.Request {
if x != nil {
return x.Request
@@ -13697,7 +13705,7 @@ var file_sliverpb_sliver_proto_rawDesc = []byte{
0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x65, 0x72, 0x73, 0x12, 0x2e, 0x0a, 0x08, 0x52, 0x65,
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x63,
0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x70, 0x62, 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
0x52, 0x08, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xb7, 0x01, 0x0a, 0x0e, 0x52,
0x52, 0x08, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xcd, 0x01, 0x0a, 0x0e, 0x52,
0x65, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x65, 0x52, 0x65, 0x71, 0x12, 0x2c, 0x0a,
0x11, 0x52, 0x65, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76,
0x61, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x11, 0x52, 0x65, 0x63, 0x6f, 0x6e, 0x6e,
@@ -13706,369 +13714,371 @@ var file_sliverpb_sliver_proto_rawDesc = []byte{
0x01, 0x28, 0x03, 0x52, 0x0e, 0x42, 0x65, 0x61, 0x63, 0x6f, 0x6e, 0x49, 0x6e, 0x74, 0x65, 0x72,
0x76, 0x61, 0x6c, 0x12, 0x22, 0x0a, 0x0c, 0x42, 0x65, 0x61, 0x63, 0x6f, 0x6e, 0x4a, 0x69, 0x74,
0x74, 0x65, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x42, 0x65, 0x61, 0x63, 0x6f,
0x6e, 0x4a, 0x69, 0x74, 0x74, 0x65, 0x72, 0x12, 0x2b, 0x0a, 0x07, 0x52, 0x65, 0x71, 0x75, 0x65,
0x73, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f,
0x6e, 0x70, 0x62, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x07, 0x52, 0x65, 0x71,
0x75, 0x65, 0x73, 0x74, 0x22, 0x3d, 0x0a, 0x0b, 0x52, 0x65, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67,
0x75, 0x72, 0x65, 0x12, 0x2e, 0x0a, 0x08, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x18,
0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x70, 0x62,
0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x52, 0x08, 0x52, 0x65, 0x73, 0x70, 0x6f,
0x6e, 0x73, 0x65, 0x22, 0x62, 0x0a, 0x0f, 0x50, 0x6f, 0x6c, 0x6c, 0x49, 0x6e, 0x74, 0x65, 0x72,
0x76, 0x61, 0x6c, 0x52, 0x65, 0x71, 0x12, 0x22, 0x0a, 0x0c, 0x50, 0x6f, 0x6c, 0x6c, 0x49, 0x6e,
0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x50, 0x6f,
0x6c, 0x6c, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x12, 0x2b, 0x0a, 0x07, 0x52, 0x65,
0x71, 0x75, 0x65, 0x73, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x63, 0x6f,
0x6d, 0x6d, 0x6f, 0x6e, 0x70, 0x62, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x07,
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x3e, 0x0a, 0x0c, 0x50, 0x6f, 0x6c, 0x6c, 0x49,
0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x12, 0x2e, 0x0a, 0x08, 0x52, 0x65, 0x73, 0x70, 0x6f,
0x6e, 0x73, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x63, 0x6f, 0x6d, 0x6d,
0x6f, 0x6e, 0x70, 0x62, 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x52, 0x08, 0x52,
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xa2, 0x02, 0x0a, 0x0d, 0x53, 0x53, 0x48, 0x43,
0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x12, 0x1a, 0x0a, 0x08, 0x55, 0x73, 0x65,
0x72, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x55, 0x73, 0x65,
0x72, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x48, 0x6f, 0x73, 0x74, 0x6e, 0x61, 0x6d,
0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x48, 0x6f, 0x73, 0x74, 0x6e, 0x61, 0x6d,
0x65, 0x12, 0x12, 0x0a, 0x04, 0x50, 0x6f, 0x72, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52,
0x04, 0x50, 0x6f, 0x72, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64,
0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x12,
0x1a, 0x0a, 0x08, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28,
0x09, 0x52, 0x08, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x50,
0x72, 0x69, 0x76, 0x4b, 0x65, 0x79, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x50, 0x72,
0x69, 0x76, 0x4b, 0x65, 0x79, 0x12, 0x1a, 0x0a, 0x08, 0x4b, 0x72, 0x62, 0x35, 0x43, 0x6f, 0x6e,
0x66, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x4b, 0x72, 0x62, 0x35, 0x43, 0x6f, 0x6e,
0x66, 0x12, 0x16, 0x0a, 0x06, 0x4b, 0x65, 0x79, 0x74, 0x61, 0x62, 0x18, 0x08, 0x20, 0x01, 0x28,
0x0c, 0x52, 0x06, 0x4b, 0x65, 0x79, 0x74, 0x61, 0x62, 0x12, 0x14, 0x0a, 0x05, 0x52, 0x65, 0x61,
0x6c, 0x6d, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x52, 0x65, 0x61, 0x6c, 0x6d, 0x12,
0x2b, 0x0a, 0x07, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b,
0x32, 0x11, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x70, 0x62, 0x2e, 0x52, 0x65, 0x71, 0x75,
0x65, 0x73, 0x74, 0x52, 0x07, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x6c, 0x0a, 0x0a,
0x53, 0x53, 0x48, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x53, 0x74,
0x64, 0x4f, 0x75, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x53, 0x74, 0x64, 0x4f,
0x75, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x53, 0x74, 0x64, 0x45, 0x72, 0x72, 0x18, 0x02, 0x20, 0x01,
0x28, 0x09, 0x52, 0x06, 0x53, 0x74, 0x64, 0x45, 0x72, 0x72, 0x12, 0x2e, 0x0a, 0x08, 0x52, 0x65,
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x63,
0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x70, 0x62, 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
0x52, 0x08, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x3a, 0x0a, 0x0b, 0x47, 0x65,
0x74, 0x50, 0x72, 0x69, 0x76, 0x73, 0x52, 0x65, 0x71, 0x12, 0x2b, 0x0a, 0x07, 0x52, 0x65, 0x71,
0x75, 0x65, 0x73, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x63, 0x6f, 0x6d,
0x6d, 0x6f, 0x6e, 0x70, 0x62, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x07, 0x52,
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0xd3, 0x01, 0x0a, 0x15, 0x57, 0x69, 0x6e, 0x64, 0x6f,
0x77, 0x73, 0x50, 0x72, 0x69, 0x76, 0x69, 0x6c, 0x65, 0x67, 0x65, 0x45, 0x6e, 0x74, 0x72, 0x79,
0x12, 0x12, 0x0a, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04,
0x4e, 0x61, 0x6d, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74,
0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x44, 0x65, 0x73, 0x63, 0x72,
0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65,
0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64,
0x12, 0x2a, 0x0a, 0x10, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x42, 0x79, 0x44, 0x65, 0x66,
0x61, 0x75, 0x6c, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x45, 0x6e, 0x61, 0x62,
0x6c, 0x65, 0x64, 0x42, 0x79, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x12, 0x18, 0x0a, 0x07,
0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x52,
0x65, 0x6d, 0x6f, 0x76, 0x65, 0x64, 0x12, 0x24, 0x0a, 0x0d, 0x55, 0x73, 0x65, 0x64, 0x46, 0x6f,
0x72, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x55,
0x73, 0x65, 0x64, 0x46, 0x6f, 0x72, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x22, 0xc5, 0x01, 0x0a,
0x08, 0x47, 0x65, 0x74, 0x50, 0x72, 0x69, 0x76, 0x73, 0x12, 0x3b, 0x0a, 0x08, 0x50, 0x72, 0x69,
0x76, 0x49, 0x6e, 0x66, 0x6f, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x73, 0x6c,
0x69, 0x76, 0x65, 0x72, 0x70, 0x62, 0x2e, 0x57, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x73, 0x50, 0x72,
0x69, 0x76, 0x69, 0x6c, 0x65, 0x67, 0x65, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x08, 0x50, 0x72,
0x69, 0x76, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x2a, 0x0a, 0x10, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73,
0x73, 0x49, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x69, 0x74, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09,
0x52, 0x10, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x49, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x69,
0x74, 0x79, 0x12, 0x20, 0x0a, 0x0b, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x4e, 0x61, 0x6d,
0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73,
0x4e, 0x61, 0x6d, 0x65, 0x12, 0x2e, 0x0a, 0x08, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x70,
0x62, 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x52, 0x08, 0x52, 0x65, 0x73, 0x70,
0x6f, 0x6e, 0x73, 0x65, 0x22, 0x8f, 0x01, 0x0a, 0x14, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65,
0x72, 0x45, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x12, 0x12, 0x0a,
0x04, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x4e, 0x61, 0x6d,
0x65, 0x12, 0x12, 0x0a, 0x04, 0x44, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52,
0x04, 0x44, 0x61, 0x74, 0x61, 0x12, 0x0e, 0x0a, 0x02, 0x4f, 0x53, 0x18, 0x03, 0x20, 0x01, 0x28,
0x09, 0x52, 0x02, 0x4f, 0x53, 0x12, 0x12, 0x0a, 0x04, 0x49, 0x6e, 0x69, 0x74, 0x18, 0x04, 0x20,
0x01, 0x28, 0x09, 0x52, 0x04, 0x49, 0x6e, 0x69, 0x74, 0x12, 0x2b, 0x0a, 0x07, 0x52, 0x65, 0x71,
0x75, 0x65, 0x73, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x63, 0x6f, 0x6d,
0x6d, 0x6f, 0x6e, 0x70, 0x62, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x07, 0x52,
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x43, 0x0a, 0x11, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74,
0x65, 0x72, 0x45, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x2e, 0x0a, 0x08, 0x52,
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e,
0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x70, 0x62, 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
0x65, 0x52, 0x08, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xa1, 0x01, 0x0a, 0x10,
0x43, 0x61, 0x6c, 0x6c, 0x45, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71,
0x12, 0x12, 0x0a, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04,
0x4e, 0x61, 0x6d, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x74,
0x6f, 0x72, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x53, 0x65, 0x72, 0x76, 0x65,
0x72, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x41, 0x72, 0x67, 0x73, 0x18, 0x03,
0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x41, 0x72, 0x67, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x45, 0x78,
0x70, 0x6f, 0x72, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x45, 0x78, 0x70, 0x6f,
0x72, 0x74, 0x12, 0x2b, 0x0a, 0x07, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x18, 0x09, 0x20,
0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x70, 0x62, 0x2e, 0x52,
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x07, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22,
0x79, 0x0a, 0x0d, 0x43, 0x61, 0x6c, 0x6c, 0x45, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e,
0x12, 0x16, 0x0a, 0x06, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c,
0x52, 0x06, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x12, 0x20, 0x0a, 0x0b, 0x53, 0x65, 0x72, 0x76,
0x65, 0x72, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x53,
0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x12, 0x2e, 0x0a, 0x08, 0x52, 0x65,
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x63,
0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x70, 0x62, 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
0x52, 0x08, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x40, 0x0a, 0x11, 0x4c, 0x69,
0x73, 0x74, 0x45, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x12,
0x2b, 0x0a, 0x07, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b,
0x32, 0x11, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x70, 0x62, 0x2e, 0x52, 0x65, 0x71, 0x75,
0x65, 0x73, 0x74, 0x52, 0x07, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x56, 0x0a, 0x0e,
0x4c, 0x69, 0x73, 0x74, 0x45, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x14,
0x0a, 0x05, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x4e,
0x61, 0x6d, 0x65, 0x73, 0x12, 0x2e, 0x0a, 0x08, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x70,
0x62, 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x52, 0x08, 0x52, 0x65, 0x73, 0x70,
0x6f, 0x6e, 0x73, 0x65, 0x22, 0x56, 0x0a, 0x17, 0x52, 0x70, 0x6f, 0x72, 0x74, 0x46, 0x77, 0x64,
0x53, 0x74, 0x6f, 0x70, 0x4c, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x65, 0x72, 0x52, 0x65, 0x71, 0x12,
0x0e, 0x0a, 0x02, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x02, 0x49, 0x44, 0x12,
0x2b, 0x0a, 0x07, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b,
0x32, 0x11, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x70, 0x62, 0x2e, 0x52, 0x65, 0x71, 0x75,
0x65, 0x73, 0x74, 0x52, 0x07, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0xcf, 0x01, 0x0a,
0x18, 0x52, 0x70, 0x6f, 0x72, 0x74, 0x46, 0x77, 0x64, 0x53, 0x74, 0x61, 0x72, 0x74, 0x4c, 0x69,
0x73, 0x74, 0x65, 0x6e, 0x65, 0x72, 0x52, 0x65, 0x71, 0x12, 0x20, 0x0a, 0x0b, 0x42, 0x69, 0x6e,
0x64, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b,
0x42, 0x69, 0x6e, 0x64, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x42,
0x69, 0x6e, 0x64, 0x50, 0x6f, 0x72, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x08, 0x42,
0x69, 0x6e, 0x64, 0x50, 0x6f, 0x72, 0x74, 0x12, 0x20, 0x0a, 0x0b, 0x46, 0x6f, 0x72, 0x77, 0x61,
0x72, 0x64, 0x50, 0x6f, 0x72, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0b, 0x46, 0x6f,
0x72, 0x77, 0x61, 0x72, 0x64, 0x50, 0x6f, 0x72, 0x74, 0x12, 0x26, 0x0a, 0x0e, 0x46, 0x6f, 0x72,
0x77, 0x61, 0x72, 0x64, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28,
0x09, 0x52, 0x0e, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73,
0x73, 0x12, 0x2b, 0x0a, 0x07, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x18, 0x09, 0x20, 0x01,
0x28, 0x0b, 0x32, 0x11, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x70, 0x62, 0x2e, 0x52, 0x65,
0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x07, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0xda,
0x01, 0x0a, 0x10, 0x52, 0x70, 0x6f, 0x72, 0x74, 0x46, 0x77, 0x64, 0x4c, 0x69, 0x73, 0x74, 0x65,
0x6e, 0x65, 0x72, 0x12, 0x0e, 0x0a, 0x02, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52,
0x02, 0x49, 0x44, 0x12, 0x20, 0x0a, 0x0b, 0x42, 0x69, 0x6e, 0x64, 0x41, 0x64, 0x64, 0x72, 0x65,
0x73, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x42, 0x69, 0x6e, 0x64, 0x41, 0x64,
0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x42, 0x69, 0x6e, 0x64, 0x50, 0x6f, 0x72,
0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x08, 0x42, 0x69, 0x6e, 0x64, 0x50, 0x6f, 0x72,
0x74, 0x12, 0x26, 0x0a, 0x0e, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x41, 0x64, 0x64, 0x72,
0x65, 0x73, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x46, 0x6f, 0x72, 0x77, 0x61,
0x72, 0x64, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x20, 0x0a, 0x0b, 0x46, 0x6f, 0x72,
0x77, 0x61, 0x72, 0x64, 0x50, 0x6f, 0x72, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0b,
0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x50, 0x6f, 0x72, 0x74, 0x12, 0x2e, 0x0a, 0x08, 0x52,
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e,
0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x70, 0x62, 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
0x65, 0x52, 0x08, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x7d, 0x0a, 0x11, 0x52,
0x70, 0x6f, 0x72, 0x74, 0x46, 0x77, 0x64, 0x4c, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x65, 0x72, 0x73,
0x12, 0x38, 0x0a, 0x09, 0x4c, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x65, 0x72, 0x73, 0x18, 0x01, 0x20,
0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x73, 0x6c, 0x69, 0x76, 0x65, 0x72, 0x70, 0x62, 0x2e, 0x52,
0x70, 0x6f, 0x72, 0x74, 0x46, 0x77, 0x64, 0x4c, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x65, 0x72, 0x52,
0x09, 0x4c, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x65, 0x72, 0x73, 0x12, 0x2e, 0x0a, 0x08, 0x52, 0x65,
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x63,
0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x70, 0x62, 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
0x52, 0x08, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x43, 0x0a, 0x14, 0x52, 0x70,
0x6f, 0x72, 0x74, 0x46, 0x77, 0x64, 0x4c, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x65, 0x72, 0x73, 0x52,
0x65, 0x71, 0x12, 0x2b, 0x0a, 0x07, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x18, 0x09, 0x20,
0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x70, 0x62, 0x2e, 0x52,
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x07, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22,
0x9e, 0x01, 0x0a, 0x08, 0x52, 0x50, 0x6f, 0x72, 0x74, 0x66, 0x77, 0x64, 0x12, 0x12, 0x0a, 0x04,
0x50, 0x6f, 0x72, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x04, 0x50, 0x6f, 0x72, 0x74,
0x12, 0x1a, 0x0a, 0x08, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x18, 0x02, 0x20, 0x01,
0x28, 0x05, 0x52, 0x08, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x12, 0x0a, 0x04,
0x48, 0x6f, 0x73, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x48, 0x6f, 0x73, 0x74,
0x12, 0x1e, 0x0a, 0x08, 0x54, 0x75, 0x6e, 0x6e, 0x65, 0x6c, 0x49, 0x44, 0x18, 0x08, 0x20, 0x01,
0x28, 0x04, 0x42, 0x02, 0x30, 0x01, 0x52, 0x08, 0x54, 0x75, 0x6e, 0x6e, 0x65, 0x6c, 0x49, 0x44,
0x6e, 0x4a, 0x69, 0x74, 0x74, 0x65, 0x72, 0x12, 0x14, 0x0a, 0x05, 0x43, 0x32, 0x55, 0x52, 0x49,
0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x43, 0x32, 0x55, 0x52, 0x49, 0x12, 0x2b, 0x0a,
0x07, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11,
0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x70, 0x62, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
0x74, 0x52, 0x07, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x3d, 0x0a, 0x0b, 0x52, 0x65,
0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x65, 0x12, 0x2e, 0x0a, 0x08, 0x52, 0x65, 0x73,
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x63, 0x6f,
0x6d, 0x6d, 0x6f, 0x6e, 0x70, 0x62, 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x52,
0x08, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x62, 0x0a, 0x0f, 0x50, 0x6f, 0x6c,
0x6c, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x52, 0x65, 0x71, 0x12, 0x22, 0x0a, 0x0c,
0x50, 0x6f, 0x6c, 0x6c, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x18, 0x01, 0x20, 0x01,
0x28, 0x03, 0x52, 0x0c, 0x50, 0x6f, 0x6c, 0x6c, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c,
0x12, 0x2b, 0x0a, 0x07, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28,
0x0b, 0x32, 0x11, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x70, 0x62, 0x2e, 0x52, 0x65, 0x71,
0x75, 0x65, 0x73, 0x74, 0x52, 0x07, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x3e, 0x0a,
0x0c, 0x50, 0x6f, 0x6c, 0x6c, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x12, 0x2e, 0x0a,
0x08, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32,
0x12, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x70, 0x62, 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f,
0x6e, 0x73, 0x65, 0x52, 0x08, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xa2, 0x02,
0x0a, 0x0d, 0x53, 0x53, 0x48, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x12,
0x1a, 0x0a, 0x08, 0x55, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28,
0x09, 0x52, 0x08, 0x55, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x48,
0x6f, 0x73, 0x74, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x48,
0x6f, 0x73, 0x74, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x50, 0x6f, 0x72, 0x74, 0x18,
0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x04, 0x50, 0x6f, 0x72, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x43,
0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x43, 0x6f,
0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72,
0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72,
0x64, 0x12, 0x18, 0x0a, 0x07, 0x50, 0x72, 0x69, 0x76, 0x4b, 0x65, 0x79, 0x18, 0x06, 0x20, 0x01,
0x28, 0x0c, 0x52, 0x07, 0x50, 0x72, 0x69, 0x76, 0x4b, 0x65, 0x79, 0x12, 0x1a, 0x0a, 0x08, 0x4b,
0x72, 0x62, 0x35, 0x43, 0x6f, 0x6e, 0x66, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x4b,
0x72, 0x62, 0x35, 0x43, 0x6f, 0x6e, 0x66, 0x12, 0x16, 0x0a, 0x06, 0x4b, 0x65, 0x79, 0x74, 0x61,
0x62, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x4b, 0x65, 0x79, 0x74, 0x61, 0x62, 0x12,
0x14, 0x0a, 0x05, 0x52, 0x65, 0x61, 0x6c, 0x6d, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05,
0x52, 0x65, 0x61, 0x6c, 0x6d, 0x12, 0x2b, 0x0a, 0x07, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x70,
0x62, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x07, 0x52, 0x65, 0x71, 0x75, 0x65,
0x73, 0x74, 0x22, 0x6c, 0x0a, 0x0a, 0x53, 0x53, 0x48, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64,
0x12, 0x16, 0x0a, 0x06, 0x53, 0x74, 0x64, 0x4f, 0x75, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
0x52, 0x06, 0x53, 0x74, 0x64, 0x4f, 0x75, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x53, 0x74, 0x64, 0x45,
0x72, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x53, 0x74, 0x64, 0x45, 0x72, 0x72,
0x12, 0x2e, 0x0a, 0x08, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x18, 0x09, 0x20, 0x01,
0x28, 0x0b, 0x32, 0x12, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x70, 0x62, 0x2e, 0x52, 0x65,
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x52, 0x08, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
0x22, 0x9e, 0x01, 0x0a, 0x0b, 0x52, 0x50, 0x6f, 0x72, 0x74, 0x66, 0x77, 0x64, 0x52, 0x65, 0x71,
0x12, 0x12, 0x0a, 0x04, 0x50, 0x6f, 0x72, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x04,
0x50, 0x6f, 0x72, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c,
0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c,
0x12, 0x12, 0x0a, 0x04, 0x48, 0x6f, 0x73, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04,
0x48, 0x6f, 0x73, 0x74, 0x12, 0x1e, 0x0a, 0x08, 0x54, 0x75, 0x6e, 0x6e, 0x65, 0x6c, 0x49, 0x44,
0x18, 0x08, 0x20, 0x01, 0x28, 0x04, 0x42, 0x02, 0x30, 0x01, 0x52, 0x08, 0x54, 0x75, 0x6e, 0x6e,
0x65, 0x6c, 0x49, 0x44, 0x12, 0x2b, 0x0a, 0x07, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x18,
0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x70, 0x62,
0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x07, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
0x74, 0x22, 0x85, 0x01, 0x0a, 0x08, 0x43, 0x68, 0x6d, 0x6f, 0x64, 0x52, 0x65, 0x71, 0x12, 0x12,
0x0a, 0x04, 0x50, 0x61, 0x74, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x50, 0x61,
0x74, 0x68, 0x12, 0x1a, 0x0a, 0x08, 0x46, 0x69, 0x6c, 0x65, 0x4d, 0x6f, 0x64, 0x65, 0x18, 0x02,
0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x46, 0x69, 0x6c, 0x65, 0x4d, 0x6f, 0x64, 0x65, 0x12, 0x1c,
0x0a, 0x09, 0x52, 0x65, 0x63, 0x75, 0x72, 0x73, 0x69, 0x76, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28,
0x08, 0x52, 0x09, 0x52, 0x65, 0x63, 0x75, 0x72, 0x73, 0x69, 0x76, 0x65, 0x12, 0x2b, 0x0a, 0x07,
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e,
0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x70, 0x62, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
0x52, 0x07, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x4b, 0x0a, 0x05, 0x43, 0x68, 0x6d,
0x6f, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x50, 0x61, 0x74, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
0x52, 0x04, 0x50, 0x61, 0x74, 0x68, 0x12, 0x2e, 0x0a, 0x08, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
0x73, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f,
0x6e, 0x70, 0x62, 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x52, 0x08, 0x52, 0x65,
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x8d, 0x01, 0x0a, 0x08, 0x43, 0x68, 0x6f, 0x77, 0x6e,
0x52, 0x65, 0x71, 0x12, 0x12, 0x0a, 0x04, 0x50, 0x61, 0x74, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28,
0x09, 0x52, 0x04, 0x50, 0x61, 0x74, 0x68, 0x12, 0x10, 0x0a, 0x03, 0x55, 0x69, 0x64, 0x18, 0x02,
0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x55, 0x69, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x47, 0x69, 0x64,
0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x47, 0x69, 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x52,
0x65, 0x63, 0x75, 0x72, 0x73, 0x69, 0x76, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09,
0x52, 0x65, 0x63, 0x75, 0x72, 0x73, 0x69, 0x76, 0x65, 0x12, 0x2b, 0x0a, 0x07, 0x52, 0x65, 0x71,
0x75, 0x65, 0x73, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x63, 0x6f, 0x6d,
0x6d, 0x6f, 0x6e, 0x70, 0x62, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x07, 0x52,
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x4b, 0x0a, 0x05, 0x43, 0x68, 0x6f, 0x77, 0x6e, 0x12,
0x12, 0x0a, 0x04, 0x50, 0x61, 0x74, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x50,
0x61, 0x74, 0x68, 0x12, 0x2e, 0x0a, 0x08, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x18,
0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x70, 0x62,
0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x52, 0x08, 0x52, 0x65, 0x73, 0x70, 0x6f,
0x6e, 0x73, 0x65, 0x22, 0x79, 0x0a, 0x0a, 0x43, 0x68, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x52, 0x65,
0x71, 0x12, 0x12, 0x0a, 0x04, 0x50, 0x61, 0x74, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
0x04, 0x50, 0x61, 0x74, 0x68, 0x12, 0x14, 0x0a, 0x05, 0x41, 0x54, 0x69, 0x6d, 0x65, 0x18, 0x02,
0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x41, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x4d,
0x54, 0x69, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x4d, 0x54, 0x69, 0x6d,
0x65, 0x12, 0x2b, 0x0a, 0x07, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x18, 0x09, 0x20, 0x01,
0x28, 0x0b, 0x32, 0x11, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x70, 0x62, 0x2e, 0x52, 0x65,
0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x07, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x4d,
0x0a, 0x07, 0x43, 0x68, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x50, 0x61, 0x74,
0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x50, 0x61, 0x74, 0x68, 0x12, 0x2e, 0x0a,
0x08, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32,
0x12, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x70, 0x62, 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f,
0x6e, 0x73, 0x65, 0x52, 0x08, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x3e, 0x0a,
0x0f, 0x4d, 0x65, 0x6d, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x71,
0x12, 0x2b, 0x0a, 0x07, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28,
0x0b, 0x32, 0x11, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x70, 0x62, 0x2e, 0x52, 0x65, 0x71,
0x75, 0x65, 0x73, 0x74, 0x52, 0x07, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x3d, 0x0a,
0x0e, 0x4d, 0x65, 0x6d, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x41, 0x64, 0x64, 0x52, 0x65, 0x71, 0x12,
0x22, 0x3a, 0x0a, 0x0b, 0x47, 0x65, 0x74, 0x50, 0x72, 0x69, 0x76, 0x73, 0x52, 0x65, 0x71, 0x12,
0x2b, 0x0a, 0x07, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b,
0x32, 0x11, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x70, 0x62, 0x2e, 0x52, 0x65, 0x71, 0x75,
0x65, 0x73, 0x74, 0x52, 0x07, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x4d, 0x0a, 0x0b,
0x4d, 0x65, 0x6d, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x41, 0x64, 0x64, 0x12, 0x0e, 0x0a, 0x02, 0x46,
0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x02, 0x46, 0x64, 0x12, 0x2e, 0x0a, 0x08, 0x52,
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e,
0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x70, 0x62, 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
0x65, 0x52, 0x08, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x4c, 0x0a, 0x0d, 0x4d,
0x65, 0x6d, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x52, 0x6d, 0x52, 0x65, 0x71, 0x12, 0x0e, 0x0a, 0x02,
0x46, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x02, 0x46, 0x64, 0x12, 0x2b, 0x0a, 0x07,
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e,
0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x70, 0x62, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
0x52, 0x07, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x4c, 0x0a, 0x0a, 0x4d, 0x65, 0x6d,
0x66, 0x69, 0x6c, 0x65, 0x73, 0x52, 0x6d, 0x12, 0x0e, 0x0a, 0x02, 0x46, 0x64, 0x18, 0x01, 0x20,
0x01, 0x28, 0x03, 0x52, 0x02, 0x46, 0x64, 0x12, 0x2e, 0x0a, 0x08, 0x52, 0x65, 0x73, 0x70, 0x6f,
0x6e, 0x73, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x63, 0x6f, 0x6d, 0x6d,
0x6f, 0x6e, 0x70, 0x62, 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x52, 0x08, 0x52,
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x73, 0x0a, 0x18, 0x52, 0x65, 0x67, 0x69, 0x73,
0x74, 0x65, 0x72, 0x57, 0x61, 0x73, 0x6d, 0x45, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e,
0x65, 0x73, 0x74, 0x52, 0x07, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0xd3, 0x01, 0x0a,
0x15, 0x57, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x73, 0x50, 0x72, 0x69, 0x76, 0x69, 0x6c, 0x65, 0x67,
0x65, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x12, 0x0a, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x01,
0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x44, 0x65,
0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52,
0x0b, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07,
0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x45,
0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x2a, 0x0a, 0x10, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65,
0x64, 0x42, 0x79, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08,
0x52, 0x10, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x42, 0x79, 0x44, 0x65, 0x66, 0x61, 0x75,
0x6c, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x64, 0x18, 0x05, 0x20,
0x01, 0x28, 0x08, 0x52, 0x07, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x64, 0x12, 0x24, 0x0a, 0x0d,
0x55, 0x73, 0x65, 0x64, 0x46, 0x6f, 0x72, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x18, 0x06, 0x20,
0x01, 0x28, 0x08, 0x52, 0x0d, 0x55, 0x73, 0x65, 0x64, 0x46, 0x6f, 0x72, 0x41, 0x63, 0x63, 0x65,
0x73, 0x73, 0x22, 0xc5, 0x01, 0x0a, 0x08, 0x47, 0x65, 0x74, 0x50, 0x72, 0x69, 0x76, 0x73, 0x12,
0x3b, 0x0a, 0x08, 0x50, 0x72, 0x69, 0x76, 0x49, 0x6e, 0x66, 0x6f, 0x18, 0x01, 0x20, 0x03, 0x28,
0x0b, 0x32, 0x1f, 0x2e, 0x73, 0x6c, 0x69, 0x76, 0x65, 0x72, 0x70, 0x62, 0x2e, 0x57, 0x69, 0x6e,
0x64, 0x6f, 0x77, 0x73, 0x50, 0x72, 0x69, 0x76, 0x69, 0x6c, 0x65, 0x67, 0x65, 0x45, 0x6e, 0x74,
0x72, 0x79, 0x52, 0x08, 0x50, 0x72, 0x69, 0x76, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x2a, 0x0a, 0x10,
0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x49, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x69, 0x74, 0x79,
0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x49,
0x6e, 0x74, 0x65, 0x67, 0x72, 0x69, 0x74, 0x79, 0x12, 0x20, 0x0a, 0x0b, 0x50, 0x72, 0x6f, 0x63,
0x65, 0x73, 0x73, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x50,
0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x2e, 0x0a, 0x08, 0x52, 0x65,
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x63,
0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x70, 0x62, 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
0x52, 0x08, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x8f, 0x01, 0x0a, 0x14, 0x52,
0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x45, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e,
0x52, 0x65, 0x71, 0x12, 0x12, 0x0a, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28,
0x09, 0x52, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x57, 0x61, 0x73, 0x6d, 0x47,
0x7a, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x57, 0x61, 0x73, 0x6d, 0x47, 0x7a, 0x12,
0x09, 0x52, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x44, 0x61, 0x74, 0x61, 0x18,
0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x44, 0x61, 0x74, 0x61, 0x12, 0x0e, 0x0a, 0x02, 0x4f,
0x53, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x4f, 0x53, 0x12, 0x12, 0x0a, 0x04, 0x49,
0x6e, 0x69, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x49, 0x6e, 0x69, 0x74, 0x12,
0x2b, 0x0a, 0x07, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b,
0x32, 0x11, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x70, 0x62, 0x2e, 0x52, 0x65, 0x71, 0x75,
0x65, 0x73, 0x74, 0x52, 0x07, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x47, 0x0a, 0x15,
0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x57, 0x61, 0x73, 0x6d, 0x45, 0x78, 0x74, 0x65,
0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x2e, 0x0a, 0x08, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e,
0x70, 0x62, 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x52, 0x08, 0x52, 0x65, 0x73,
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x5d, 0x0a, 0x1a, 0x44, 0x65, 0x72, 0x65, 0x67, 0x69, 0x73,
0x74, 0x65, 0x72, 0x57, 0x61, 0x73, 0x6d, 0x45, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e,
0x52, 0x65, 0x71, 0x12, 0x12, 0x0a, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28,
0x09, 0x52, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x2b, 0x0a, 0x07, 0x52, 0x65, 0x71, 0x75, 0x65,
0x65, 0x73, 0x74, 0x52, 0x07, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x43, 0x0a, 0x11,
0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x45, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f,
0x6e, 0x12, 0x2e, 0x0a, 0x08, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x18, 0x09, 0x20,
0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x70, 0x62, 0x2e, 0x52,
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x52, 0x08, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
0x65, 0x22, 0xa1, 0x01, 0x0a, 0x10, 0x43, 0x61, 0x6c, 0x6c, 0x45, 0x78, 0x74, 0x65, 0x6e, 0x73,
0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x12, 0x12, 0x0a, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x01,
0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x53, 0x65,
0x72, 0x76, 0x65, 0x72, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52,
0x0b, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x12, 0x12, 0x0a, 0x04,
0x41, 0x72, 0x67, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x41, 0x72, 0x67, 0x73,
0x12, 0x16, 0x0a, 0x06, 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09,
0x52, 0x06, 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x2b, 0x0a, 0x07, 0x52, 0x65, 0x71, 0x75,
0x65, 0x73, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x63, 0x6f, 0x6d, 0x6d,
0x6f, 0x6e, 0x70, 0x62, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x07, 0x52, 0x65,
0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x79, 0x0a, 0x0d, 0x43, 0x61, 0x6c, 0x6c, 0x45, 0x78, 0x74,
0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74,
0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x12, 0x20,
0x0a, 0x0b, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x18, 0x02, 0x20,
0x01, 0x28, 0x08, 0x52, 0x0b, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x74, 0x6f, 0x72, 0x65,
0x12, 0x2e, 0x0a, 0x08, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x18, 0x09, 0x20, 0x01,
0x28, 0x0b, 0x32, 0x12, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x70, 0x62, 0x2e, 0x52, 0x65,
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x52, 0x08, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
0x22, 0x40, 0x0a, 0x11, 0x4c, 0x69, 0x73, 0x74, 0x45, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f,
0x6e, 0x73, 0x52, 0x65, 0x71, 0x12, 0x2b, 0x0a, 0x07, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x70,
0x62, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x07, 0x52, 0x65, 0x71, 0x75, 0x65,
0x73, 0x74, 0x22, 0x56, 0x0a, 0x0e, 0x4c, 0x69, 0x73, 0x74, 0x45, 0x78, 0x74, 0x65, 0x6e, 0x73,
0x69, 0x6f, 0x6e, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x01, 0x20,
0x03, 0x28, 0x09, 0x52, 0x05, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x12, 0x2e, 0x0a, 0x08, 0x52, 0x65,
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x63,
0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x70, 0x62, 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
0x52, 0x08, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x56, 0x0a, 0x17, 0x52, 0x70,
0x6f, 0x72, 0x74, 0x46, 0x77, 0x64, 0x53, 0x74, 0x6f, 0x70, 0x4c, 0x69, 0x73, 0x74, 0x65, 0x6e,
0x65, 0x72, 0x52, 0x65, 0x71, 0x12, 0x0e, 0x0a, 0x02, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28,
0x0d, 0x52, 0x02, 0x49, 0x44, 0x12, 0x2b, 0x0a, 0x07, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x70,
0x62, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x07, 0x52, 0x65, 0x71, 0x75, 0x65,
0x73, 0x74, 0x22, 0xcf, 0x01, 0x0a, 0x18, 0x52, 0x70, 0x6f, 0x72, 0x74, 0x46, 0x77, 0x64, 0x53,
0x74, 0x61, 0x72, 0x74, 0x4c, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x65, 0x72, 0x52, 0x65, 0x71, 0x12,
0x20, 0x0a, 0x0b, 0x42, 0x69, 0x6e, 0x64, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01,
0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x42, 0x69, 0x6e, 0x64, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73,
0x73, 0x12, 0x1a, 0x0a, 0x08, 0x42, 0x69, 0x6e, 0x64, 0x50, 0x6f, 0x72, 0x74, 0x18, 0x02, 0x20,
0x01, 0x28, 0x0d, 0x52, 0x08, 0x42, 0x69, 0x6e, 0x64, 0x50, 0x6f, 0x72, 0x74, 0x12, 0x20, 0x0a,
0x0b, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x50, 0x6f, 0x72, 0x74, 0x18, 0x03, 0x20, 0x01,
0x28, 0x0d, 0x52, 0x0b, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x50, 0x6f, 0x72, 0x74, 0x12,
0x26, 0x0a, 0x0e, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73,
0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64,
0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x2b, 0x0a, 0x07, 0x52, 0x65, 0x71, 0x75, 0x65,
0x73, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f,
0x6e, 0x70, 0x62, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x07, 0x52, 0x65, 0x71,
0x75, 0x65, 0x73, 0x74, 0x22, 0x44, 0x0a, 0x15, 0x4c, 0x69, 0x73, 0x74, 0x57, 0x61, 0x73, 0x6d,
0x45, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x12, 0x2b, 0x0a,
0x07, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11,
0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x70, 0x62, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
0x74, 0x52, 0x07, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x5a, 0x0a, 0x12, 0x4c, 0x69,
0x73, 0x74, 0x57, 0x61, 0x73, 0x6d, 0x45, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73,
0x12, 0x14, 0x0a, 0x05, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52,
0x05, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x12, 0x2e, 0x0a, 0x08, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
0x75, 0x65, 0x73, 0x74, 0x22, 0xda, 0x01, 0x0a, 0x10, 0x52, 0x70, 0x6f, 0x72, 0x74, 0x46, 0x77,
0x64, 0x4c, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x65, 0x72, 0x12, 0x0e, 0x0a, 0x02, 0x49, 0x44, 0x18,
0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x02, 0x49, 0x44, 0x12, 0x20, 0x0a, 0x0b, 0x42, 0x69, 0x6e,
0x64, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b,
0x42, 0x69, 0x6e, 0x64, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x42,
0x69, 0x6e, 0x64, 0x50, 0x6f, 0x72, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x08, 0x42,
0x69, 0x6e, 0x64, 0x50, 0x6f, 0x72, 0x74, 0x12, 0x26, 0x0a, 0x0e, 0x46, 0x6f, 0x72, 0x77, 0x61,
0x72, 0x64, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52,
0x0e, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12,
0x20, 0x0a, 0x0b, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x50, 0x6f, 0x72, 0x74, 0x18, 0x05,
0x20, 0x01, 0x28, 0x0d, 0x52, 0x0b, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x50, 0x6f, 0x72,
0x74, 0x12, 0x2e, 0x0a, 0x08, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x18, 0x09, 0x20,
0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x70, 0x62, 0x2e, 0x52,
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x52, 0x08, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
0x65, 0x22, 0x7d, 0x0a, 0x11, 0x52, 0x70, 0x6f, 0x72, 0x74, 0x46, 0x77, 0x64, 0x4c, 0x69, 0x73,
0x74, 0x65, 0x6e, 0x65, 0x72, 0x73, 0x12, 0x38, 0x0a, 0x09, 0x4c, 0x69, 0x73, 0x74, 0x65, 0x6e,
0x65, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x73, 0x6c, 0x69, 0x76,
0x65, 0x72, 0x70, 0x62, 0x2e, 0x52, 0x70, 0x6f, 0x72, 0x74, 0x46, 0x77, 0x64, 0x4c, 0x69, 0x73,
0x74, 0x65, 0x6e, 0x65, 0x72, 0x52, 0x09, 0x4c, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x65, 0x72, 0x73,
0x12, 0x2e, 0x0a, 0x08, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x18, 0x09, 0x20, 0x01,
0x28, 0x0b, 0x32, 0x12, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x70, 0x62, 0x2e, 0x52, 0x65,
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x52, 0x08, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
0x22, 0x43, 0x0a, 0x14, 0x52, 0x70, 0x6f, 0x72, 0x74, 0x46, 0x77, 0x64, 0x4c, 0x69, 0x73, 0x74,
0x65, 0x6e, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x12, 0x2b, 0x0a, 0x07, 0x52, 0x65, 0x71, 0x75,
0x65, 0x73, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x63, 0x6f, 0x6d, 0x6d,
0x6f, 0x6e, 0x70, 0x62, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x07, 0x52, 0x65,
0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x9e, 0x01, 0x0a, 0x08, 0x52, 0x50, 0x6f, 0x72, 0x74, 0x66,
0x77, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x50, 0x6f, 0x72, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d,
0x52, 0x04, 0x50, 0x6f, 0x72, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63,
0x6f, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63,
0x6f, 0x6c, 0x12, 0x12, 0x0a, 0x04, 0x48, 0x6f, 0x73, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09,
0x52, 0x04, 0x48, 0x6f, 0x73, 0x74, 0x12, 0x1e, 0x0a, 0x08, 0x54, 0x75, 0x6e, 0x6e, 0x65, 0x6c,
0x49, 0x44, 0x18, 0x08, 0x20, 0x01, 0x28, 0x04, 0x42, 0x02, 0x30, 0x01, 0x52, 0x08, 0x54, 0x75,
0x6e, 0x6e, 0x65, 0x6c, 0x49, 0x44, 0x12, 0x2e, 0x0a, 0x08, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
0x73, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f,
0x6e, 0x70, 0x62, 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x52, 0x08, 0x52, 0x65,
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xa8, 0x02, 0x0a, 0x14, 0x45, 0x78, 0x65, 0x63, 0x57,
0x61, 0x73, 0x6d, 0x45, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x12,
0x12, 0x0a, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x4e,
0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x41, 0x72, 0x67, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28,
0x09, 0x52, 0x04, 0x41, 0x72, 0x67, 0x73, 0x12, 0x20, 0x0a, 0x0b, 0x49, 0x6e, 0x74, 0x65, 0x72,
0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x49, 0x6e,
0x74, 0x65, 0x72, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x12, 0x3f, 0x0a, 0x05, 0x4d, 0x65, 0x6d,
0x46, 0x53, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x73, 0x6c, 0x69, 0x76, 0x65,
0x72, 0x70, 0x62, 0x2e, 0x45, 0x78, 0x65, 0x63, 0x57, 0x61, 0x73, 0x6d, 0x45, 0x78, 0x74, 0x65,
0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x2e, 0x4d, 0x65, 0x6d, 0x46, 0x53, 0x45, 0x6e,
0x74, 0x72, 0x79, 0x52, 0x05, 0x4d, 0x65, 0x6d, 0x46, 0x53, 0x12, 0x1e, 0x0a, 0x08, 0x54, 0x75,
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x9e, 0x01, 0x0a, 0x0b, 0x52, 0x50, 0x6f, 0x72, 0x74,
0x66, 0x77, 0x64, 0x52, 0x65, 0x71, 0x12, 0x12, 0x0a, 0x04, 0x50, 0x6f, 0x72, 0x74, 0x18, 0x01,
0x20, 0x01, 0x28, 0x0d, 0x52, 0x04, 0x50, 0x6f, 0x72, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x50, 0x72,
0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x50, 0x72,
0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x12, 0x0a, 0x04, 0x48, 0x6f, 0x73, 0x74, 0x18, 0x03,
0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x48, 0x6f, 0x73, 0x74, 0x12, 0x1e, 0x0a, 0x08, 0x54, 0x75,
0x6e, 0x6e, 0x65, 0x6c, 0x49, 0x44, 0x18, 0x08, 0x20, 0x01, 0x28, 0x04, 0x42, 0x02, 0x30, 0x01,
0x52, 0x08, 0x54, 0x75, 0x6e, 0x6e, 0x65, 0x6c, 0x49, 0x44, 0x12, 0x2b, 0x0a, 0x07, 0x52, 0x65,
0x71, 0x75, 0x65, 0x73, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x63, 0x6f,
0x6d, 0x6d, 0x6f, 0x6e, 0x70, 0x62, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x07,
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x38, 0x0a, 0x0a, 0x4d, 0x65, 0x6d, 0x46, 0x53,
0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01,
0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65,
0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38,
0x01, 0x22, 0x8f, 0x01, 0x0a, 0x11, 0x45, 0x78, 0x65, 0x63, 0x57, 0x61, 0x73, 0x6d, 0x45, 0x78,
0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x53, 0x74, 0x64, 0x6f, 0x75,
0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x53, 0x74, 0x64, 0x6f, 0x75, 0x74, 0x12,
0x16, 0x0a, 0x06, 0x53, 0x74, 0x64, 0x65, 0x72, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52,
0x06, 0x53, 0x74, 0x64, 0x65, 0x72, 0x72, 0x12, 0x1a, 0x0a, 0x08, 0x45, 0x78, 0x69, 0x74, 0x43,
0x6f, 0x64, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x08, 0x45, 0x78, 0x69, 0x74, 0x43,
0x6f, 0x64, 0x65, 0x12, 0x2e, 0x0a, 0x08, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x18,
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x85, 0x01, 0x0a, 0x08, 0x43, 0x68, 0x6d, 0x6f,
0x64, 0x52, 0x65, 0x71, 0x12, 0x12, 0x0a, 0x04, 0x50, 0x61, 0x74, 0x68, 0x18, 0x01, 0x20, 0x01,
0x28, 0x09, 0x52, 0x04, 0x50, 0x61, 0x74, 0x68, 0x12, 0x1a, 0x0a, 0x08, 0x46, 0x69, 0x6c, 0x65,
0x4d, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x46, 0x69, 0x6c, 0x65,
0x4d, 0x6f, 0x64, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x52, 0x65, 0x63, 0x75, 0x72, 0x73, 0x69, 0x76,
0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x52, 0x65, 0x63, 0x75, 0x72, 0x73, 0x69,
0x76, 0x65, 0x12, 0x2b, 0x0a, 0x07, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x18, 0x09, 0x20,
0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x70, 0x62, 0x2e, 0x52,
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x07, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22,
0x4b, 0x0a, 0x05, 0x43, 0x68, 0x6d, 0x6f, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x50, 0x61, 0x74, 0x68,
0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x50, 0x61, 0x74, 0x68, 0x12, 0x2e, 0x0a, 0x08,
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12,
0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x70, 0x62, 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
0x73, 0x65, 0x52, 0x08, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x8d, 0x01, 0x0a,
0x08, 0x43, 0x68, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x71, 0x12, 0x12, 0x0a, 0x04, 0x50, 0x61, 0x74,
0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x50, 0x61, 0x74, 0x68, 0x12, 0x10, 0x0a,
0x03, 0x55, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x55, 0x69, 0x64, 0x12,
0x10, 0x0a, 0x03, 0x47, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x47, 0x69,
0x64, 0x12, 0x1c, 0x0a, 0x09, 0x52, 0x65, 0x63, 0x75, 0x72, 0x73, 0x69, 0x76, 0x65, 0x18, 0x04,
0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x52, 0x65, 0x63, 0x75, 0x72, 0x73, 0x69, 0x76, 0x65, 0x12,
0x2b, 0x0a, 0x07, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b,
0x32, 0x11, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x70, 0x62, 0x2e, 0x52, 0x65, 0x71, 0x75,
0x65, 0x73, 0x74, 0x52, 0x07, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x4b, 0x0a, 0x05,
0x43, 0x68, 0x6f, 0x77, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x50, 0x61, 0x74, 0x68, 0x18, 0x01, 0x20,
0x01, 0x28, 0x09, 0x52, 0x04, 0x50, 0x61, 0x74, 0x68, 0x12, 0x2e, 0x0a, 0x08, 0x52, 0x65, 0x73,
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x63, 0x6f,
0x6d, 0x6d, 0x6f, 0x6e, 0x70, 0x62, 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x52,
0x08, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x79, 0x0a, 0x0a, 0x43, 0x68, 0x74,
0x69, 0x6d, 0x65, 0x73, 0x52, 0x65, 0x71, 0x12, 0x12, 0x0a, 0x04, 0x50, 0x61, 0x74, 0x68, 0x18,
0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x50, 0x61, 0x74, 0x68, 0x12, 0x14, 0x0a, 0x05, 0x41,
0x54, 0x69, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x41, 0x54, 0x69, 0x6d,
0x65, 0x12, 0x14, 0x0a, 0x05, 0x4d, 0x54, 0x69, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03,
0x52, 0x05, 0x4d, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x2b, 0x0a, 0x07, 0x52, 0x65, 0x71, 0x75, 0x65,
0x73, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f,
0x6e, 0x70, 0x62, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x07, 0x52, 0x65, 0x71,
0x75, 0x65, 0x73, 0x74, 0x22, 0x4d, 0x0a, 0x07, 0x43, 0x68, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x12,
0x12, 0x0a, 0x04, 0x50, 0x61, 0x74, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x50,
0x61, 0x74, 0x68, 0x12, 0x2e, 0x0a, 0x08, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x18,
0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x70, 0x62,
0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x52, 0x08, 0x52, 0x65, 0x73, 0x70, 0x6f,
0x6e, 0x73, 0x65, 0x22, 0x56, 0x0a, 0x0b, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x52,
0x65, 0x71, 0x12, 0x1a, 0x0a, 0x08, 0x48, 0x6f, 0x73, 0x74, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01,
0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x48, 0x6f, 0x73, 0x74, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x2b,
0x0a, 0x07, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32,
0x11, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x70, 0x62, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65,
0x73, 0x74, 0x52, 0x07, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x7b, 0x0a, 0x10, 0x53,
0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x52, 0x65, 0x71, 0x12,
0x3a, 0x0a, 0x0b, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x18, 0x01,
0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x73, 0x6c, 0x69, 0x76, 0x65, 0x72, 0x70, 0x62, 0x2e,
0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x52, 0x0b,
0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x2b, 0x0a, 0x07, 0x52,
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x63,
0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x70, 0x62, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52,
0x07, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0xd6, 0x01, 0x0a, 0x0e, 0x53, 0x65, 0x72,
0x76, 0x69, 0x63, 0x65, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x4e,
0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x12,
0x20, 0x0a, 0x0b, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x02,
0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x4e, 0x61, 0x6d,
0x65, 0x12, 0x20, 0x0a, 0x0b, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e,
0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74,
0x69, 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x04, 0x20,
0x01, 0x28, 0x0d, 0x52, 0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x20, 0x0a, 0x0b, 0x53,
0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x54, 0x79, 0x70, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d,
0x52, 0x0b, 0x53, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x54, 0x79, 0x70, 0x65, 0x12, 0x18, 0x0a,
0x07, 0x42, 0x69, 0x6e, 0x50, 0x61, 0x74, 0x68, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07,
0x42, 0x69, 0x6e, 0x50, 0x61, 0x74, 0x68, 0x12, 0x18, 0x0a, 0x07, 0x41, 0x63, 0x63, 0x6f, 0x75,
0x6e, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e,
0x74, 0x22, 0x84, 0x01, 0x0a, 0x08, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x12, 0x32,
0x0a, 0x07, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32,
0x18, 0x2e, 0x73, 0x6c, 0x69, 0x76, 0x65, 0x72, 0x70, 0x62, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69,
0x63, 0x65, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x52, 0x07, 0x44, 0x65, 0x74, 0x61, 0x69,
0x6c, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28,
0x09, 0x52, 0x05, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x2e, 0x0a, 0x08, 0x52, 0x65, 0x73, 0x70,
0x6f, 0x6e, 0x73, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x63, 0x6f, 0x6d,
0x6d, 0x6f, 0x6e, 0x70, 0x62, 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x52, 0x08,
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x8b, 0x01, 0x0a, 0x0d, 0x53, 0x65, 0x72,
0x76, 0x69, 0x63, 0x65, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x12, 0x30, 0x0a, 0x06, 0x44, 0x65,
0x74, 0x61, 0x69, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x73, 0x6c, 0x69,
0x76, 0x65, 0x72, 0x70, 0x62, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x44, 0x65, 0x74,
0x61, 0x69, 0x6c, 0x73, 0x52, 0x06, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x12, 0x18, 0x0a, 0x07,
0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x4d,
0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x2e, 0x0a, 0x08, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
0x73, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f,
0x6e, 0x70, 0x62, 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x52, 0x08, 0x52, 0x65,
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x80, 0x01, 0x0a, 0x15, 0x53, 0x74, 0x61, 0x72, 0x74,
0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x42, 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x52, 0x65, 0x71,
0x12, 0x3a, 0x0a, 0x0b, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x18,
0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x73, 0x6c, 0x69, 0x76, 0x65, 0x72, 0x70, 0x62,
0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x52,
0x0b, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x2b, 0x0a, 0x07,
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e,
0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x70, 0x62, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
0x52, 0x07, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2a, 0x49, 0x0a, 0x0c, 0x52, 0x65, 0x67,
0x69, 0x73, 0x74, 0x72, 0x79, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x6e, 0x6b,
0x6e, 0x6f, 0x77, 0x6e, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x42, 0x69, 0x6e, 0x61, 0x72, 0x79,
0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x10, 0x02, 0x12, 0x09,
0x0a, 0x05, 0x44, 0x57, 0x4f, 0x52, 0x44, 0x10, 0x03, 0x12, 0x09, 0x0a, 0x05, 0x51, 0x57, 0x4f,
0x52, 0x44, 0x10, 0x04, 0x2a, 0x2c, 0x0a, 0x09, 0x50, 0x69, 0x76, 0x6f, 0x74, 0x54, 0x79, 0x70,
0x65, 0x12, 0x07, 0x0a, 0x03, 0x54, 0x43, 0x50, 0x10, 0x00, 0x12, 0x07, 0x0a, 0x03, 0x55, 0x44,
0x50, 0x10, 0x01, 0x12, 0x0d, 0x0a, 0x09, 0x4e, 0x61, 0x6d, 0x65, 0x64, 0x50, 0x69, 0x70, 0x65,
0x10, 0x02, 0x2a, 0x33, 0x0a, 0x0f, 0x50, 0x65, 0x65, 0x72, 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72,
0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x10, 0x0a, 0x0c, 0x53, 0x45, 0x4e, 0x44, 0x5f, 0x46, 0x41,
0x49, 0x4c, 0x55, 0x52, 0x45, 0x10, 0x00, 0x12, 0x0e, 0x0a, 0x0a, 0x44, 0x49, 0x53, 0x43, 0x4f,
0x4e, 0x4e, 0x45, 0x43, 0x54, 0x10, 0x01, 0x42, 0x2f, 0x5a, 0x2d, 0x67, 0x69, 0x74, 0x68, 0x75,
0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x62, 0x69, 0x73, 0x68, 0x6f, 0x70, 0x66, 0x6f, 0x78, 0x2f,
0x73, 0x6c, 0x69, 0x76, 0x65, 0x72, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f,
0x73, 0x6c, 0x69, 0x76, 0x65, 0x72, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
0x6e, 0x73, 0x65, 0x22, 0x3e, 0x0a, 0x0f, 0x4d, 0x65, 0x6d, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x4c,
0x69, 0x73, 0x74, 0x52, 0x65, 0x71, 0x12, 0x2b, 0x0a, 0x07, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e,
0x70, 0x62, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x07, 0x52, 0x65, 0x71, 0x75,
0x65, 0x73, 0x74, 0x22, 0x3d, 0x0a, 0x0e, 0x4d, 0x65, 0x6d, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x41,
0x64, 0x64, 0x52, 0x65, 0x71, 0x12, 0x2b, 0x0a, 0x07, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x70,
0x62, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x07, 0x52, 0x65, 0x71, 0x75, 0x65,
0x73, 0x74, 0x22, 0x4d, 0x0a, 0x0b, 0x4d, 0x65, 0x6d, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x41, 0x64,
0x64, 0x12, 0x0e, 0x0a, 0x02, 0x46, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x02, 0x46,
0x64, 0x12, 0x2e, 0x0a, 0x08, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x18, 0x09, 0x20,
0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x70, 0x62, 0x2e, 0x52,
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x52, 0x08, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
0x65, 0x22, 0x4c, 0x0a, 0x0d, 0x4d, 0x65, 0x6d, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x52, 0x6d, 0x52,
0x65, 0x71, 0x12, 0x0e, 0x0a, 0x02, 0x46, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x02,
0x46, 0x64, 0x12, 0x2b, 0x0a, 0x07, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x18, 0x09, 0x20,
0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x70, 0x62, 0x2e, 0x52,
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x07, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22,
0x4c, 0x0a, 0x0a, 0x4d, 0x65, 0x6d, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x52, 0x6d, 0x12, 0x0e, 0x0a,
0x02, 0x46, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x02, 0x46, 0x64, 0x12, 0x2e, 0x0a,
0x08, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32,
0x12, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x70, 0x62, 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f,
0x6e, 0x73, 0x65, 0x52, 0x08, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x73, 0x0a,
0x18, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x57, 0x61, 0x73, 0x6d, 0x45, 0x78, 0x74,
0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x12, 0x12, 0x0a, 0x04, 0x4e, 0x61, 0x6d,
0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x16, 0x0a,
0x06, 0x57, 0x61, 0x73, 0x6d, 0x47, 0x7a, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x57,
0x61, 0x73, 0x6d, 0x47, 0x7a, 0x12, 0x2b, 0x0a, 0x07, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x70,
0x62, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x07, 0x52, 0x65, 0x71, 0x75, 0x65,
0x73, 0x74, 0x22, 0x47, 0x0a, 0x15, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x57, 0x61,
0x73, 0x6d, 0x45, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x2e, 0x0a, 0x08, 0x52,
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e,
0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x70, 0x62, 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
0x65, 0x52, 0x08, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x5d, 0x0a, 0x1a, 0x44,
0x65, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x57, 0x61, 0x73, 0x6d, 0x45, 0x78, 0x74,
0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x12, 0x12, 0x0a, 0x04, 0x4e, 0x61, 0x6d,
0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x2b, 0x0a,
0x07, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11,
0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x70, 0x62, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
0x74, 0x52, 0x07, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x44, 0x0a, 0x15, 0x4c, 0x69,
0x73, 0x74, 0x57, 0x61, 0x73, 0x6d, 0x45, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73,
0x52, 0x65, 0x71, 0x12, 0x2b, 0x0a, 0x07, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x18, 0x09,
0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x70, 0x62, 0x2e,
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x07, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
0x22, 0x5a, 0x0a, 0x12, 0x4c, 0x69, 0x73, 0x74, 0x57, 0x61, 0x73, 0x6d, 0x45, 0x78, 0x74, 0x65,
0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x18,
0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x12, 0x2e, 0x0a, 0x08,
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12,
0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x70, 0x62, 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
0x73, 0x65, 0x52, 0x08, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xa8, 0x02, 0x0a,
0x14, 0x45, 0x78, 0x65, 0x63, 0x57, 0x61, 0x73, 0x6d, 0x45, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69,
0x6f, 0x6e, 0x52, 0x65, 0x71, 0x12, 0x12, 0x0a, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20,
0x01, 0x28, 0x09, 0x52, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x41, 0x72, 0x67,
0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x04, 0x41, 0x72, 0x67, 0x73, 0x12, 0x20, 0x0a,
0x0b, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x18, 0x03, 0x20, 0x01,
0x28, 0x08, 0x52, 0x0b, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x12,
0x3f, 0x0a, 0x05, 0x4d, 0x65, 0x6d, 0x46, 0x53, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x29,
0x2e, 0x73, 0x6c, 0x69, 0x76, 0x65, 0x72, 0x70, 0x62, 0x2e, 0x45, 0x78, 0x65, 0x63, 0x57, 0x61,
0x73, 0x6d, 0x45, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x2e, 0x4d,
0x65, 0x6d, 0x46, 0x53, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x05, 0x4d, 0x65, 0x6d, 0x46, 0x53,
0x12, 0x1e, 0x0a, 0x08, 0x54, 0x75, 0x6e, 0x6e, 0x65, 0x6c, 0x49, 0x44, 0x18, 0x08, 0x20, 0x01,
0x28, 0x04, 0x42, 0x02, 0x30, 0x01, 0x52, 0x08, 0x54, 0x75, 0x6e, 0x6e, 0x65, 0x6c, 0x49, 0x44,
0x12, 0x2b, 0x0a, 0x07, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28,
0x0b, 0x32, 0x11, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x70, 0x62, 0x2e, 0x52, 0x65, 0x71,
0x75, 0x65, 0x73, 0x74, 0x52, 0x07, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x38, 0x0a,
0x0a, 0x4d, 0x65, 0x6d, 0x46, 0x53, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b,
0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a,
0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61,
0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x8f, 0x01, 0x0a, 0x11, 0x45, 0x78, 0x65, 0x63,
0x57, 0x61, 0x73, 0x6d, 0x45, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x0a,
0x06, 0x53, 0x74, 0x64, 0x6f, 0x75, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x53,
0x74, 0x64, 0x6f, 0x75, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x53, 0x74, 0x64, 0x65, 0x72, 0x72, 0x18,
0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x53, 0x74, 0x64, 0x65, 0x72, 0x72, 0x12, 0x1a, 0x0a,
0x08, 0x45, 0x78, 0x69, 0x74, 0x43, 0x6f, 0x64, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52,
0x08, 0x45, 0x78, 0x69, 0x74, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x2e, 0x0a, 0x08, 0x52, 0x65, 0x73,
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x63, 0x6f,
0x6d, 0x6d, 0x6f, 0x6e, 0x70, 0x62, 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x52,
0x08, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x56, 0x0a, 0x0b, 0x53, 0x65, 0x72,
0x76, 0x69, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x12, 0x1a, 0x0a, 0x08, 0x48, 0x6f, 0x73, 0x74,
0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x48, 0x6f, 0x73, 0x74,
0x6e, 0x61, 0x6d, 0x65, 0x12, 0x2b, 0x0a, 0x07, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x18,
0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x70, 0x62,
0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x07, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
0x74, 0x22, 0x7b, 0x0a, 0x10, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x44, 0x65, 0x74, 0x61,
0x69, 0x6c, 0x52, 0x65, 0x71, 0x12, 0x3a, 0x0a, 0x0b, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65,
0x49, 0x6e, 0x66, 0x6f, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x73, 0x6c, 0x69,
0x76, 0x65, 0x72, 0x70, 0x62, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x49, 0x6e, 0x66,
0x6f, 0x52, 0x65, 0x71, 0x52, 0x0b, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x49, 0x6e, 0x66,
0x6f, 0x12, 0x2b, 0x0a, 0x07, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x18, 0x09, 0x20, 0x01,
0x28, 0x0b, 0x32, 0x11, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x70, 0x62, 0x2e, 0x52, 0x65,
0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x07, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0xd6,
0x01, 0x0a, 0x0e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c,
0x73, 0x12, 0x12, 0x0a, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
0x04, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79,
0x4e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x44, 0x69, 0x73, 0x70,
0x6c, 0x61, 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x44, 0x65, 0x73, 0x63, 0x72,
0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x44, 0x65,
0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x53, 0x74, 0x61,
0x74, 0x75, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, 0x53, 0x74, 0x61, 0x74, 0x75,
0x73, 0x12, 0x20, 0x0a, 0x0b, 0x53, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x54, 0x79, 0x70, 0x65,
0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0b, 0x53, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x54,
0x79, 0x70, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x42, 0x69, 0x6e, 0x50, 0x61, 0x74, 0x68, 0x18, 0x06,
0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x42, 0x69, 0x6e, 0x50, 0x61, 0x74, 0x68, 0x12, 0x18, 0x0a,
0x07, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07,
0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x84, 0x01, 0x0a, 0x08, 0x53, 0x65, 0x72, 0x76,
0x69, 0x63, 0x65, 0x73, 0x12, 0x32, 0x0a, 0x07, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x18,
0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x73, 0x6c, 0x69, 0x76, 0x65, 0x72, 0x70, 0x62,
0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x52,
0x07, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x45, 0x72, 0x72, 0x6f,
0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x2e,
0x0a, 0x08, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b,
0x32, 0x12, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x70, 0x62, 0x2e, 0x52, 0x65, 0x73, 0x70,
0x6f, 0x6e, 0x73, 0x65, 0x52, 0x08, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x8b,
0x01, 0x0a, 0x0d, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c,
0x12, 0x30, 0x0a, 0x06, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b,
0x32, 0x18, 0x2e, 0x73, 0x6c, 0x69, 0x76, 0x65, 0x72, 0x70, 0x62, 0x2e, 0x53, 0x65, 0x72, 0x76,
0x69, 0x63, 0x65, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x52, 0x06, 0x44, 0x65, 0x74, 0x61,
0x69, 0x6c, 0x12, 0x18, 0x0a, 0x07, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20,
0x01, 0x28, 0x09, 0x52, 0x07, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x2e, 0x0a, 0x08,
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12,
0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x70, 0x62, 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
0x73, 0x65, 0x52, 0x08, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x80, 0x01, 0x0a,
0x15, 0x53, 0x74, 0x61, 0x72, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x42, 0x79, 0x4e,
0x61, 0x6d, 0x65, 0x52, 0x65, 0x71, 0x12, 0x3a, 0x0a, 0x0b, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63,
0x65, 0x49, 0x6e, 0x66, 0x6f, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x73, 0x6c,
0x69, 0x76, 0x65, 0x72, 0x70, 0x62, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x49, 0x6e,
0x66, 0x6f, 0x52, 0x65, 0x71, 0x52, 0x0b, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x49, 0x6e,
0x66, 0x6f, 0x12, 0x2b, 0x0a, 0x07, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x18, 0x09, 0x20,
0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x70, 0x62, 0x2e, 0x52,
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x07, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2a,
0x49, 0x0a, 0x0c, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x54, 0x79, 0x70, 0x65, 0x12,
0x0b, 0x0a, 0x07, 0x55, 0x6e, 0x6b, 0x6e, 0x6f, 0x77, 0x6e, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06,
0x42, 0x69, 0x6e, 0x61, 0x72, 0x79, 0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x53, 0x74, 0x72, 0x69,
0x6e, 0x67, 0x10, 0x02, 0x12, 0x09, 0x0a, 0x05, 0x44, 0x57, 0x4f, 0x52, 0x44, 0x10, 0x03, 0x12,
0x09, 0x0a, 0x05, 0x51, 0x57, 0x4f, 0x52, 0x44, 0x10, 0x04, 0x2a, 0x2c, 0x0a, 0x09, 0x50, 0x69,
0x76, 0x6f, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x07, 0x0a, 0x03, 0x54, 0x43, 0x50, 0x10, 0x00,
0x12, 0x07, 0x0a, 0x03, 0x55, 0x44, 0x50, 0x10, 0x01, 0x12, 0x0d, 0x0a, 0x09, 0x4e, 0x61, 0x6d,
0x65, 0x64, 0x50, 0x69, 0x70, 0x65, 0x10, 0x02, 0x2a, 0x33, 0x0a, 0x0f, 0x50, 0x65, 0x65, 0x72,
0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x10, 0x0a, 0x0c, 0x53,
0x45, 0x4e, 0x44, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x10, 0x00, 0x12, 0x0e, 0x0a,
0x0a, 0x44, 0x49, 0x53, 0x43, 0x4f, 0x4e, 0x4e, 0x45, 0x43, 0x54, 0x10, 0x01, 0x42, 0x2f, 0x5a,
0x2d, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x62, 0x69, 0x73, 0x68,
0x6f, 0x70, 0x66, 0x6f, 0x78, 0x2f, 0x73, 0x6c, 0x69, 0x76, 0x65, 0x72, 0x2f, 0x70, 0x72, 0x6f,
0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x73, 0x6c, 0x69, 0x76, 0x65, 0x72, 0x70, 0x62, 0x62, 0x06,
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
+1
View File
@@ -1069,6 +1069,7 @@ message ReconfigureReq {
int64 ReconnectInterval = 1;
int64 BeaconInterval = 2;
int64 BeaconJitter = 3;
string C2URI = 4;
commonpb.Request Request = 9;
}
Loaded 100 of 108 files, more files were not shown because too many files have changed in this diff. Show more