Skip to content

Commit d7dce58

Browse files
authored
Merge pull request #2168 from BishopFox/feature/mutli-console
Feature/mutli console
2 parents afd5789 + 1511413 commit d7dce58

68 files changed

Lines changed: 2581 additions & 606 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

client/assets/settings.go

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"encoding/json"
2323
"os"
2424
"path/filepath"
25+
"strings"
2526

2627
"gopkg.in/yaml.v3"
2728
)
@@ -31,6 +32,18 @@ const (
3132
settingsLegacyFileName = "tui-settings.json"
3233
)
3334

35+
const (
36+
// Accepted values for tui-settings.yaml "prompt:":
37+
// - host: show "[host]" prefix on sliver-client, "[server]" on server console
38+
// - operator-host: show "[operator@host]" prefix on sliver-client, "[server]" on server console
39+
// - basic: show "sliver >" (no prefix)
40+
// - custom: render full prompt from prompt_template
41+
PromptStyleHost = "host"
42+
PromptStyleOperatorHost = "operator-host"
43+
PromptStyleBasic = "basic"
44+
PromptStyleCustom = "custom"
45+
)
46+
3447
// ClientSettings - Client JSON config
3548
type ClientSettings struct {
3649
TableStyle string `json:"tables" yaml:"tables"`
@@ -41,6 +54,8 @@ type ClientSettings struct {
4154
VimMode bool `json:"vim_mode" yaml:"vim_mode"`
4255
UserConnect bool `json:"user_connect" yaml:"user_connect"`
4356
ConsoleLogs bool `json:"console_logs" yaml:"console_logs"`
57+
PromptStyle string `json:"prompt" yaml:"prompt"`
58+
PromptTemplate string `json:"prompt_template" yaml:"prompt_template"`
4459
}
4560

4661
// LoadSettings - Load the client settings from disk
@@ -67,6 +82,8 @@ func LoadSettings() (*ClientSettings, error) {
6782
return defaultSettings(), err
6883
}
6984

85+
// Ensure any missing/unknown values are coerced to a supported prompt style.
86+
settings.PromptStyle = NormalizePromptStyle(settings.PromptStyle)
7087
if err := SaveSettings(settings); err != nil {
7188
return settings, err
7289
}
@@ -78,6 +95,38 @@ func LoadSettings() (*ClientSettings, error) {
7895
return settings, nil
7996
}
8097

98+
// NormalizePromptStyle canonicalizes prompt style strings and returns a safe
99+
// default if the value is empty/unknown.
100+
func NormalizePromptStyle(v string) string {
101+
switch strings.ToLower(strings.TrimSpace(v)) {
102+
case PromptStyleOperatorHost:
103+
return PromptStyleOperatorHost
104+
case PromptStyleHost:
105+
return PromptStyleHost
106+
case PromptStyleBasic:
107+
return PromptStyleBasic
108+
case PromptStyleCustom:
109+
return PromptStyleCustom
110+
111+
// Backward compatible aliases.
112+
case "show host":
113+
return PromptStyleHost
114+
case "show user and host":
115+
return PromptStyleOperatorHost
116+
case "show operator and host":
117+
return PromptStyleOperatorHost
118+
case "operator@host":
119+
return PromptStyleOperatorHost
120+
case "user@host":
121+
return PromptStyleOperatorHost
122+
123+
case "":
124+
return PromptStyleHost
125+
default:
126+
return PromptStyleHost
127+
}
128+
}
129+
81130
func defaultSettings() *ClientSettings {
82131
return &ClientSettings{
83132
TableStyle: "SliverDefault",
@@ -87,9 +136,13 @@ func defaultSettings() *ClientSettings {
87136
AlwaysOverflow: false,
88137
VimMode: false,
89138
ConsoleLogs: true,
139+
PromptStyle: PromptStyleHost,
140+
PromptTemplate: DefaultPromptTemplate,
90141
}
91142
}
92143

144+
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 -}}`
145+
93146
// SaveSettings - Save the current settings to disk
94147
func SaveSettings(settings *ClientSettings) error {
95148
rootDir, _ := filepath.Abs(GetRootAppDir())

client/assets/settings_test.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ always_overflow: true
2121
vim_mode: true
2222
user_connect: true
2323
console_logs: false
24+
prompt: "basic"
25+
prompt_template: "{{.Host}}"
2426
`)
2527
if err := os.WriteFile(settingsPath, data, 0o600); err != nil {
2628
t.Fatalf("failed to write yaml settings: %v", err)
@@ -54,6 +56,12 @@ console_logs: false
5456
if settings.ConsoleLogs {
5557
t.Fatalf("expected ConsoleLogs false")
5658
}
59+
if settings.PromptStyle != PromptStyleBasic {
60+
t.Fatalf("expected PromptStyle %q, got %q", PromptStyleBasic, settings.PromptStyle)
61+
}
62+
if settings.PromptTemplate != "{{.Host}}" {
63+
t.Fatalf("expected PromptTemplate %q, got %q", "{{.Host}}", settings.PromptTemplate)
64+
}
5765
}
5866

5967
func TestLoadSettingsMigratesLegacyJSON(t *testing.T) {
@@ -71,6 +79,8 @@ func TestLoadSettingsMigratesLegacyJSON(t *testing.T) {
7179
VimMode: true,
7280
UserConnect: true,
7381
ConsoleLogs: false,
82+
PromptStyle: PromptStyleOperatorHost,
83+
PromptTemplate: "{{.Operator}}@{{.Host}} sliver > ",
7484
}
7585
data, err := json.MarshalIndent(legacy, "", " ")
7686
if err != nil {
@@ -87,6 +97,12 @@ func TestLoadSettingsMigratesLegacyJSON(t *testing.T) {
8797
if settings.TableStyle != "Legacy" {
8898
t.Fatalf("expected TableStyle %q, got %q", "Legacy", settings.TableStyle)
8999
}
100+
if settings.PromptStyle != PromptStyleOperatorHost {
101+
t.Fatalf("expected PromptStyle %q, got %q", PromptStyleOperatorHost, settings.PromptStyle)
102+
}
103+
if settings.PromptTemplate != "{{.Operator}}@{{.Host}} sliver > " {
104+
t.Fatalf("expected PromptTemplate %q, got %q", "{{.Operator}}@{{.Host}} sliver > ", settings.PromptTemplate)
105+
}
90106
if _, err := os.Stat(filepath.Join(rootDir, settingsFileName)); err != nil {
91107
t.Fatalf("expected yaml settings to exist: %v", err)
92108
}
@@ -108,6 +124,12 @@ func TestLoadSettingsWritesDefault(t *testing.T) {
108124
if settings.TableStyle != "SliverDefault" {
109125
t.Fatalf("expected default TableStyle %q, got %q", "SliverDefault", settings.TableStyle)
110126
}
127+
if settings.PromptStyle != PromptStyleHost {
128+
t.Fatalf("expected default PromptStyle %q, got %q", PromptStyleHost, settings.PromptStyle)
129+
}
130+
if settings.PromptTemplate != DefaultPromptTemplate {
131+
t.Fatalf("expected default PromptTemplate %q, got %q", DefaultPromptTemplate, settings.PromptTemplate)
132+
}
111133
rootDir, _ := filepath.Abs(GetRootAppDir())
112134
if _, err := os.Stat(filepath.Join(rootDir, settingsFileName)); err != nil {
113135
t.Fatalf("expected default settings to be written: %v", err)

client/cli/config.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,16 +26,16 @@ import (
2626
"github.com/bishopfox/sliver/client/forms"
2727
)
2828

29-
func selectConfig() *assets.ClientConfig {
29+
func selectConfig() (string, *assets.ClientConfig) {
3030
configs := assets.GetConfigs()
3131

3232
if len(configs) == 0 {
33-
return nil
33+
return "", nil
3434
}
3535

3636
if len(configs) == 1 {
37-
for _, config := range configs {
38-
return config
37+
for key, config := range configs {
38+
return key, config
3939
}
4040
}
4141

@@ -49,8 +49,8 @@ func selectConfig() *assets.ClientConfig {
4949
err := forms.Select("Select a server:", keys, &selection)
5050
if err != nil {
5151
fmt.Println(err.Error())
52-
return nil
52+
return "", nil
5353
}
5454

55-
return configs[selection]
55+
return selection, configs[selection]
5656
}

client/cli/connection_lost.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package cli
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"os"
7+
8+
"google.golang.org/grpc"
9+
"google.golang.org/grpc/connectivity"
10+
)
11+
12+
func handleConnectionLost(ln *grpc.ClientConn) {
13+
if ln == nil {
14+
return
15+
}
16+
currentState := ln.GetState()
17+
// currentState should be "Ready" when the connection is established.
18+
if ln.WaitForStateChange(context.Background(), currentState) {
19+
newState := ln.GetState()
20+
// newState will be "Idle" if the connection is lost.
21+
if newState == connectivity.Idle {
22+
fmt.Println("\nLost connection to server. Exiting now.")
23+
os.Exit(1)
24+
}
25+
}
26+
}

client/cli/console.go

Lines changed: 4 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,7 @@ package cli
1919
*/
2020

2121
import (
22-
"context"
2322
"fmt"
24-
"os"
2523

2624
"github.com/bishopfox/sliver/client/assets"
2725
"github.com/bishopfox/sliver/client/command"
@@ -30,7 +28,6 @@ import (
3028
"github.com/bishopfox/sliver/protobuf/rpcpb"
3129
"github.com/spf13/cobra"
3230
"google.golang.org/grpc"
33-
"google.golang.org/grpc/connectivity"
3431
)
3532

3633
// consoleCmd generates the console with required pre/post runners.
@@ -46,16 +43,14 @@ func consoleCmd(con *console.SliverClient) *cobra.Command {
4643
}
4744

4845
func consoleRunnerCmd(con *console.SliverClient, run bool) (pre, post func(cmd *cobra.Command, args []string) error) {
49-
var ln *grpc.ClientConn
50-
5146
pre = func(cmd *cobra.Command, _ []string) error {
5247

5348
configs := assets.GetConfigs()
5449
if len(configs) == 0 {
5550
fmt.Printf("No config files found at %s (see --help)\n", assets.GetConfigDir())
5651
return nil
5752
}
58-
config := selectConfig()
53+
configKey, config := selectConfig()
5954
if config == nil {
6055
return nil
6156
}
@@ -72,40 +67,20 @@ func consoleRunnerCmd(con *console.SliverClient, run bool) (pre, post func(cmd *
7267
}
7368

7469
var rpc rpcpb.SliverRPCClient
70+
var ln *grpc.ClientConn
7571

7672
rpc, ln, err = transport.MTLSConnect(config)
7773
if err != nil {
7874
fmt.Printf("Connection to server failed %s", err)
7975
return nil
8076
}
81-
82-
// Wait for any connection state changes and exit if the connection is lost.
83-
go handleConnectionLost(ln)
84-
85-
return console.StartClient(con, rpc, command.ServerCommands(con, nil), command.SliverCommands(con), run, rcScript)
77+
return console.StartClient(con, rpc, ln, &console.ConnectionDetails{ConfigKey: configKey, Config: config}, command.ServerCommands(con, nil), command.SliverCommands(con), run, rcScript)
8678
}
8779

8880
// Close the RPC connection once exiting
8981
post = func(_ *cobra.Command, _ []string) error {
90-
if ln != nil {
91-
return ln.Close()
92-
}
93-
94-
return nil
82+
return con.CloseConnection()
9583
}
9684

9785
return pre, post
9886
}
99-
100-
func handleConnectionLost(ln *grpc.ClientConn) {
101-
currentState := ln.GetState()
102-
// currentState should be "Ready" when the connection is established.
103-
if ln.WaitForStateChange(context.Background(), currentState) {
104-
newState := ln.GetState()
105-
// newState will be "Idle" if the connection is lost.
106-
if newState == connectivity.Idle {
107-
fmt.Println("\nLost connection to server. Exiting now.")
108-
os.Exit(1)
109-
}
110-
}
111-
}

client/command/armory/armory.go

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -428,7 +428,7 @@ func PrintArmoryPackages(aliases []*alias.AliasManifest, exts []*extensions.Exte
428428

429429
tw := table.NewWriter()
430430
tw.SetStyle(settings.GetTableStyle(con))
431-
tw.SetTitle(console.Bold + "Packages" + console.Normal)
431+
tw.SetTitle(console.StyleBold.Render("Packages"))
432432

433433
urlMargin := 150 // Extra margin needed to show URL column
434434

@@ -493,26 +493,26 @@ func PrintArmoryPackages(aliases []*alias.AliasManifest, exts []*extensions.Exte
493493

494494
rows := []table.Row{}
495495
for _, pkg := range entries {
496-
color := console.Normal
496+
style := console.StyleNormal
497497
if extensions.CmdExists(pkg.CommandName, sliverMenu.Command) {
498-
color = console.Green
498+
style = console.StyleGreen
499499
}
500500
if con.Settings.SmallTermWidth+urlMargin < width {
501501
rows = append(rows, table.Row{
502-
fmt.Sprintf(color+"%s"+console.Normal, pkg.ArmoryName),
503-
fmt.Sprintf(color+"%s"+console.Normal, pkg.CommandName),
504-
fmt.Sprintf(color+"%s"+console.Normal, pkg.Version),
505-
fmt.Sprintf(color+"%s"+console.Normal, pkg.Type),
506-
fmt.Sprintf(color+"%s"+console.Normal, pkg.Help),
507-
fmt.Sprintf(color+"%s"+console.Normal, pkg.URL),
502+
style.Render(pkg.ArmoryName),
503+
style.Render(pkg.CommandName),
504+
style.Render(pkg.Version),
505+
style.Render(pkg.Type),
506+
style.Render(pkg.Help),
507+
style.Render(pkg.URL),
508508
})
509509
} else {
510510
rows = append(rows, table.Row{
511-
fmt.Sprintf(color+"%s"+console.Normal, pkg.ArmoryName),
512-
fmt.Sprintf(color+"%s"+console.Normal, pkg.CommandName),
513-
fmt.Sprintf(color+"%s"+console.Normal, pkg.Version),
514-
fmt.Sprintf(color+"%s"+console.Normal, pkg.Type),
515-
fmt.Sprintf(color+"%s"+console.Normal, pkg.Help),
511+
style.Render(pkg.ArmoryName),
512+
style.Render(pkg.CommandName),
513+
style.Render(pkg.Version),
514+
style.Render(pkg.Type),
515+
style.Render(pkg.Help),
516516
})
517517
}
518518
}
@@ -524,7 +524,7 @@ func PrintArmoryPackages(aliases []*alias.AliasManifest, exts []*extensions.Exte
524524
func PrintArmoryBundles(bundles []*ArmoryBundle, con *console.SliverClient) {
525525
tw := table.NewWriter()
526526
tw.SetStyle(settings.GetTableStyle(con))
527-
tw.SetTitle(console.Bold + "Bundles" + console.Normal)
527+
tw.SetTitle(console.StyleBold.Render("Bundles"))
528528
tw.AppendHeader(table.Row{
529529
"Armory Name",
530530
"Name",

client/command/armory/update.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -257,7 +257,7 @@ func displayAvailableUpdates(con *console.SliverClient, updateKeys []UpdateIdent
257257
if len(extensionUpdates) != 1 {
258258
extensionSuffix = "s"
259259
}
260-
tw.SetTitle(console.Bold + fmt.Sprintf(title, len(aliasUpdates), aliasSuffix, len(extensionUpdates), extensionSuffix) + console.Normal)
260+
tw.SetTitle(console.StyleBold.Render(fmt.Sprintf(title, len(aliasUpdates), aliasSuffix, len(extensionUpdates), extensionSuffix)))
261261
tw.AppendHeader(table.Row{
262262
"Package Name",
263263
"Package Type",

0 commit comments

Comments
 (0)