diff --git a/go.mod b/go.mod index e5672bdb..994653cb 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/bettercap/bettercap go 1.12 require ( + github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d github.com/adrianmo/go-nmea v1.3.0 github.com/antchfx/jsonquery v1.1.4 github.com/antchfx/xpath v1.1.10 // indirect @@ -38,6 +39,7 @@ require ( github.com/pkg/errors v0.9.1 // indirect github.com/robertkrimen/otto v0.0.0-20200922221731-ef014fd054ac github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07 + github.com/thoj/go-ircevent v0.0.0-20190807115034-8e7ce4b5a1eb golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a // indirect golang.org/x/net v0.0.0-20200925080053-05aa5d4ee321 golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d // indirect diff --git a/go.sum b/go.sum index cb1c90d0..85e00a17 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,7 @@ github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8= +github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo= github.com/adrianmo/go-nmea v1.1.0 h1:0NILSj14nj6LvVQHo/afHbyPgGz5qvp5PM6jmMyAQzY= github.com/adrianmo/go-nmea v1.1.0/go.mod h1:HHPxPAm2kmev+61qmkZh7xgZF/7qHtSpsWppip2Ipv8= github.com/adrianmo/go-nmea v1.3.0 h1:BFrLRj/oIh+DYujIKpuQievq7X3NDHYq57kNgsfr2GY= @@ -151,6 +153,8 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07 h1:UyzmZLoiDWMRywV4DUYb9Fbt8uiOSooupjTq10vpvnU= github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA= +github.com/thoj/go-ircevent v0.0.0-20190807115034-8e7ce4b5a1eb h1:EavwSqheIJl3nb91HhkL73DwnT2Fk8W3yM7T7TuLZvA= +github.com/thoj/go-ircevent v0.0.0-20190807115034-8e7ce4b5a1eb/go.mod h1:I0ZT9x8wStY6VOxtNOrLpnDURFs7HS0z1e1vhuKUEVc= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= diff --git a/modules/ble/ble_show.go b/modules/ble/ble_show.go index cb1ee7ab..6243a6f0 100644 --- a/modules/ble/ble_show.go +++ b/modules/ble/ble_show.go @@ -3,7 +3,6 @@ package ble import ( - "os" "sort" "time" @@ -146,7 +145,7 @@ func (mod *BLERecon) Show() error { } if len(rows) > 0 { - tui.Table(os.Stdout, mod.colNames(hasName), rows) + tui.Table(mod.Session.Events.Stdout, mod.colNames(hasName), rows) mod.Session.Refresh() } diff --git a/modules/ble/ble_show_services.go b/modules/ble/ble_show_services.go index c244e169..833dfc48 100644 --- a/modules/ble/ble_show_services.go +++ b/modules/ble/ble_show_services.go @@ -5,7 +5,6 @@ package ble import ( "encoding/binary" "fmt" - "os" "strconv" "strings" @@ -406,7 +405,7 @@ func (mod *BLERecon) showServices(p gatt.Peripheral, services []*gatt.Service) { if wantsToWrite && !foundToWrite { mod.Error("writable characteristics %s not found.", mod.writeUUID) } else { - tui.Table(os.Stdout, columns, rows) + tui.Table(mod.Session.Events.Stdout, columns, rows) mod.Session.Refresh() } } diff --git a/modules/c2/c2.go b/modules/c2/c2.go new file mode 100644 index 00000000..ab908f0f --- /dev/null +++ b/modules/c2/c2.go @@ -0,0 +1,354 @@ +package c2 + +import ( + "bytes" + "crypto/tls" + "fmt" + "github.com/acarl005/stripansi" + "github.com/bettercap/bettercap/modules/events_stream" + "github.com/bettercap/bettercap/session" + "github.com/evilsocket/islazy/log" + "github.com/evilsocket/islazy/str" + irc "github.com/thoj/go-ircevent" + "strings" + "text/template" +) + +type settings struct { + server string + ssl bool + nick string + user string + password string + operator string + controlChannel string + eventsChannel string + outputChannel string +} + +type C2 struct { + session.SessionModule + + settings settings + stream *events_stream.EventsStream + templates map[string]*template.Template + channels map[string]string + client *irc.Connection + eventBus session.EventBus + quit chan bool +} + +type eventContext struct { + Session *session.Session + Event session.Event +} + +func NewC2(s *session.Session) *C2 { + mod := &C2{ + SessionModule: session.NewSessionModule("c2", s), + stream: events_stream.NewEventsStream(s), + templates: make(map[string]*template.Template), + channels: make(map[string]string), + quit: make(chan bool), + settings: settings{ + server: "localhost:6697", + ssl: true, + nick: "bettercap", + user: "bettercap", + password: "password", + operator: "admin", + eventsChannel: "#events", + outputChannel: "#events", + controlChannel: "#events", + }, + } + + mod.AddParam(session.NewStringParameter("c2.server", + mod.settings.server, + "", + "IRC server address and port.")) + + mod.AddParam(session.NewBoolParameter("c2.server.tls", + "true", + "Enable or disable TLS.")) + + mod.AddParam(session.NewStringParameter("c2.operator", + mod.settings.operator, + "", + "IRC nickname of the user allowed to run commands.")) + + mod.AddParam(session.NewStringParameter("c2.nick", + mod.settings.nick, + "", + "IRC nickname.")) + + mod.AddParam(session.NewStringParameter("c2.username", + mod.settings.user, + "", + "IRC username.")) + + mod.AddParam(session.NewStringParameter("c2.password", + mod.settings.password, + "", + "IRC server password.")) + + mod.AddParam(session.NewStringParameter("c2.channel.output", + mod.settings.outputChannel, + "", + "IRC channel to send commands output to.")) + + mod.AddParam(session.NewStringParameter("c2.channel.events", + mod.settings.eventsChannel, + "", + "IRC channel to send events to.")) + + mod.AddParam(session.NewStringParameter("c2.channel.control", + mod.settings.controlChannel, + "", + "IRC channel to receive commands from.")) + + mod.AddHandler(session.NewModuleHandler("c2 on", "", + "Start the C2 module.", + func(args []string) error { + return mod.Start() + })) + + mod.AddHandler(session.NewModuleHandler("c2 off", "", + "Stop the C2 module.", + func(args []string) error { + return mod.Stop() + })) + + mod.AddHandler(session.NewModuleHandler("c2.channel.set EVENT_TYPE CHANNEL", + "c2.channel.set ([^\\s]+) (.+)", + "Set a specific channel to report events of this type.", + func(args []string) error { + eventType := args[0] + channel := args[1] + + mod.Debug("setting channel for event %s: %v", eventType, channel) + mod.channels[eventType] = channel + return nil + })) + + mod.AddHandler(session.NewModuleHandler("c2.channel.clear EVENT_TYPE", + "c2.channel.clear ([^\\s]+)", + "Clear the channel to use for a specific event type.", + func(args []string) error { + eventType := args[0] + if _, found := mod.channels[args[0]]; found { + delete(mod.channels, eventType) + mod.Debug("cleared channel for %s", eventType) + } else { + return fmt.Errorf("channel for event %s not set", args[0]) + } + return nil + })) + + mod.AddHandler(session.NewModuleHandler("c2.template.set EVENT_TYPE TEMPLATE", + "c2.template.set ([^\\s]+) (.+)", + "Set the reporting template to use for a specific event type.", + func(args []string) error { + eventType := args[0] + eventTemplate := args[1] + + parsed, err := template.New(eventType).Parse(eventTemplate) + if err != nil { + return err + } + + mod.Debug("setting template for event %s: %v", eventType, parsed) + mod.templates[eventType] = parsed + return nil + })) + + mod.AddHandler(session.NewModuleHandler("c2.template.clear EVENT_TYPE", + "c2.template.clear ([^\\s]+)", + "Clear the reporting template to use for a specific event type.", + func(args []string) error { + eventType := args[0] + if _, found := mod.templates[args[0]]; found { + delete(mod.templates, eventType) + mod.Debug("cleared template for %s", eventType) + } else { + return fmt.Errorf("template for event %s not set", args[0]) + } + return nil + })) + + mod.Session.Events.OnPrint(mod.onPrint) + + return mod +} + +func (mod *C2) Name() string { + return "c2" +} + +func (mod *C2) Description() string { + return "A CnC module that connects to an IRC server for reporting and commands." +} + +func (mod *C2) Author() string { + return "Simone Margaritelli " +} + +func (mod *C2) Configure() (err error) { + if mod.Running() { + return session.ErrAlreadyStarted(mod.Name()) + } + + if err, mod.settings.server = mod.StringParam("c2.server"); err != nil { + return err + } else if err, mod.settings.ssl = mod.BoolParam("c2.server.tls"); err != nil { + return err + } else if err, mod.settings.nick = mod.StringParam("c2.nick"); err != nil { + return err + } else if err, mod.settings.user = mod.StringParam("c2.username"); err != nil { + return err + } else if err, mod.settings.password = mod.StringParam("c2.password"); err != nil { + return err + } else if err, mod.settings.operator = mod.StringParam("c2.operator"); err != nil { + return err + } else if err, mod.settings.eventsChannel = mod.StringParam("c2.channel.events"); err != nil { + return err + } else if err, mod.settings.controlChannel = mod.StringParam("c2.channel.control"); err != nil { + return err + } else if err, mod.settings.outputChannel = mod.StringParam("c2.channel.output"); err != nil { + return err + } + + mod.eventBus = mod.Session.Events.Listen() + + mod.client = irc.IRC(mod.settings.nick, mod.settings.user) + + if log.Level == log.DEBUG { + mod.client.VerboseCallbackHandler = true + mod.client.Debug = true + } + mod.client.Password = mod.settings.password + mod.client.UseTLS = mod.settings.ssl + mod.client.TLSConfig = &tls.Config{ + InsecureSkipVerify: true, // TODO: pass this by parameter? + } + + mod.client.AddCallback("PRIVMSG", func(event *irc.Event) { + channel := event.Arguments[0] + message := event.Message() + from := event.Nick + + if from != mod.settings.operator { + mod.client.Privmsg(event.Nick, "nope") + return + } + + if channel != mod.settings.controlChannel && channel != mod.settings.nick { + mod.Debug("from:%s on:%s - '%s'", from, channel, message) + return + } + + mod.Debug("from:%s on:%s - '%s'", from, channel, message) + + parts := strings.SplitN(message, " ", 2) + cmd := parts[0] + args := "" + if len(parts) > 1 { + args = parts[1] + } + + if cmd == "join" { + mod.client.Join(args) + } else if cmd == "part" { + mod.client.Part(args) + } else if cmd == "nick" { + mod.client.Nick(args) + } else if err = mod.Session.Run(message); err == nil { + // mod.client.Privmsg(event.Nick, "ok") + } else { + mod.client.Privmsgf(event.Nick, "error: %v", stripansi.Strip(err.Error())) + } + }) + + mod.client.AddCallback("001", func(e *irc.Event) { + mod.Debug("got 101") + mod.client.Join(mod.settings.controlChannel) + mod.client.Join(mod.settings.outputChannel) + mod.client.Join(mod.settings.eventsChannel) + }) + + return mod.client.Connect(mod.settings.server) +} + +func (mod *C2) onPrint(format string, args...interface{}) { + if !mod.Running() { + return + } + + msg := stripansi.Strip(str.Trim(fmt.Sprintf(format, args...))) + + for _, line := range strings.Split(msg, "\n") { + mod.client.Privmsg(mod.settings.outputChannel, line) + } +} + +func (mod *C2) onEvent(e session.Event) { + if mod.Session.EventsIgnoreList.Ignored(e) { + return + } + + // default channel or event specific channel? + channel := mod.settings.eventsChannel + if custom, found := mod.channels[e.Tag]; found { + channel = custom + } + + var out bytes.Buffer + if tpl, found := mod.templates[e.Tag]; found { + // use a custom template to render this event + if err := tpl.Execute(&out, eventContext{ + Session: mod.Session, + Event: e, + }); err != nil { + fmt.Fprintf(&out, "%v", err) + } + } else { + // use the default view to render this event + mod.stream.Render(&out, e) + } + + // make sure colors and in general bash escape sequences are removed + msg := stripansi.Strip(str.Trim(string(out.Bytes()))) + + mod.client.Privmsg(channel, msg) +} + +func (mod *C2) Start() error { + if err := mod.Configure(); err != nil { + return err + } + + return mod.SetRunning(true, func() { + mod.Info("started") + + for mod.Running() { + var e session.Event + select { + case e = <-mod.eventBus: + mod.onEvent(e) + + case <-mod.quit: + mod.Debug("got quit") + return + } + } + }) +} + +func (mod *C2) Stop() error { + return mod.SetRunning(false, func() { + mod.quit <- true + mod.Session.Events.Unlisten(mod.eventBus) + mod.client.Quit() + mod.client.Disconnect() + }) +} diff --git a/modules/caplets/caplets.go b/modules/caplets/caplets.go index 648df2ac..9310b0f6 100644 --- a/modules/caplets/caplets.go +++ b/modules/caplets/caplets.go @@ -91,7 +91,7 @@ func (mod *CapletsModule) Show() error { }) } - tui.Table(os.Stdout, colNames, rows) + tui.Table(mod.Session.Events.Stdout, colNames, rows) return nil } @@ -106,8 +106,8 @@ func (mod *CapletsModule) Paths() error { rows = append(rows, []string{path}) } - tui.Table(os.Stdout, colNames, rows) - fmt.Printf("(paths can be customized by defining the %s environment variable)\n", tui.Bold(caplets.EnvVarName)) + tui.Table(mod.Session.Events.Stdout, colNames, rows) + mod.Printf("(paths can be customized by defining the %s environment variable)\n", tui.Bold(caplets.EnvVarName)) return nil } diff --git a/modules/events_stream/events_rotation.go b/modules/events_stream/events_rotation.go new file mode 100644 index 00000000..8c217650 --- /dev/null +++ b/modules/events_stream/events_rotation.go @@ -0,0 +1,61 @@ +package events_stream + +import ( + "fmt" + "github.com/evilsocket/islazy/zip" + "os" + "time" +) + +func (mod *EventsStream) doRotation() { + if mod.output == os.Stdout { + return + } else if !mod.rotation.Enabled { + return + } + + output, isFile := mod.output.(*os.File) + if !isFile { + return + } + + mod.rotation.Lock() + defer mod.rotation.Unlock() + + doRotate := false + if info, err := output.Stat(); err == nil { + if mod.rotation.How == "size" { + doRotate = float64(info.Size()) >= float64(mod.rotation.Period*1024*1024) + } else if mod.rotation.How == "time" { + doRotate = info.ModTime().Unix()%int64(mod.rotation.Period) == 0 + } + } + + if doRotate { + var err error + + name := fmt.Sprintf("%s-%s", mod.outputName, time.Now().Format(mod.rotation.Format)) + + if err := output.Close(); err != nil { + mod.Printf("could not close log for rotation: %s\n", err) + return + } + + if err := os.Rename(mod.outputName, name); err != nil { + mod.Printf("could not rename %s to %s: %s\n", mod.outputName, name, err) + } else if mod.rotation.Compress { + zipName := fmt.Sprintf("%s.zip", name) + if err = zip.Files(zipName, []string{name}); err != nil { + mod.Printf("error creating %s: %s", zipName, err) + } else if err = os.Remove(name); err != nil { + mod.Printf("error deleting %s: %s", name, err) + } + } + + mod.output, err = os.OpenFile(mod.outputName, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) + if err != nil { + mod.Printf("could not open %s: %s", mod.outputName, err) + } + } +} + diff --git a/modules/events_stream/events_stream.go b/modules/events_stream/events_stream.go index c52828fc..c4c76822 100644 --- a/modules/events_stream/events_stream.go +++ b/modules/events_stream/events_stream.go @@ -2,6 +2,7 @@ package events_stream import ( "fmt" + "io" "os" "strconv" "sync" @@ -27,7 +28,7 @@ type EventsStream struct { session.SessionModule timeFormat string outputName string - output *os.File + output io.Writer rotation rotation triggerList *TriggerList waitFor string @@ -149,13 +150,13 @@ func NewEventsStream(s *session.Session) *EventsStream { "Print the list of filters used to ignore events.", func(args []string) error { if mod.Session.EventsIgnoreList.Empty() { - fmt.Printf("Ignore filters list is empty.\n") + mod.Printf("Ignore filters list is empty.\n") } else { mod.Session.EventsIgnoreList.RLock() defer mod.Session.EventsIgnoreList.RUnlock() for _, filter := range mod.Session.EventsIgnoreList.Filters() { - fmt.Printf(" '%s'\n", string(filter)) + mod.Printf(" '%s'\n", string(filter)) } } return nil @@ -322,7 +323,7 @@ func (mod *EventsStream) Show(limit int) error { } if numSelected := len(selected); numSelected > 0 { - fmt.Println() + mod.Printf("\n") for i := range selected { mod.View(selected[numSelected-1-i], false) } @@ -360,7 +361,9 @@ func (mod *EventsStream) Stop() error { return mod.SetRunning(false, func() { mod.quit <- true if mod.output != os.Stdout { - mod.output.Close() + if fp, ok := mod.output.(*os.File); ok { + fp.Close() + } } }) } diff --git a/modules/events_stream/events_triggers.go b/modules/events_stream/events_triggers.go index 0c50c9cc..5c2cc228 100644 --- a/modules/events_stream/events_triggers.go +++ b/modules/events_stream/events_triggers.go @@ -1,8 +1,6 @@ package events_stream import ( - "os" - "github.com/bettercap/bettercap/session" "github.com/evilsocket/islazy/tui" @@ -41,7 +39,7 @@ func (mod *EventsStream) showTriggers() error { }) if len(rows) > 0 { - tui.Table(os.Stdout, colNames, rows) + tui.Table(mod.Session.Events.Stdout, colNames, rows) mod.Session.Refresh() } diff --git a/modules/events_stream/events_view.go b/modules/events_stream/events_view.go index c529db58..d479f0e3 100644 --- a/modules/events_stream/events_view.go +++ b/modules/events_stream/events_view.go @@ -2,12 +2,11 @@ package events_stream import ( "fmt" - "os" - "strings" - "time" - "github.com/bettercap/bettercap/network" "github.com/bettercap/bettercap/session" + "io" + "os" + "strings" "github.com/bettercap/bettercap/modules/net_sniff" "github.com/bettercap/bettercap/modules/syn_scan" @@ -15,18 +14,17 @@ import ( "github.com/google/go-github/github" "github.com/evilsocket/islazy/tui" - "github.com/evilsocket/islazy/zip" ) -func (mod *EventsStream) viewLogEvent(e session.Event) { - fmt.Fprintf(mod.output, "[%s] [%s] [%s] %s\n", +func (mod *EventsStream) viewLogEvent(output io.Writer, e session.Event) { + fmt.Fprintf(output, "[%s] [%s] [%s] %s\n", e.Time.Format(mod.timeFormat), tui.Green(e.Tag), e.Label(), e.Data.(session.LogMessage).Message) } -func (mod *EventsStream) viewEndpointEvent(e session.Event) { +func (mod *EventsStream) viewEndpointEvent(output io.Writer, e session.Event) { t := e.Data.(*network.Endpoint) vend := "" name := "" @@ -42,7 +40,7 @@ func (mod *EventsStream) viewEndpointEvent(e session.Event) { } if e.Tag == "endpoint.new" { - fmt.Fprintf(mod.output, "[%s] [%s] endpoint %s%s detected as %s%s.\n", + fmt.Fprintf(output, "[%s] [%s] endpoint %s%s detected as %s%s.\n", e.Time.Format(mod.timeFormat), tui.Green(e.Tag), tui.Bold(t.IpAddress), @@ -50,7 +48,7 @@ func (mod *EventsStream) viewEndpointEvent(e session.Event) { tui.Green(t.HwAddress), tui.Dim(vend)) } else if e.Tag == "endpoint.lost" { - fmt.Fprintf(mod.output, "[%s] [%s] endpoint %s%s %s%s lost.\n", + fmt.Fprintf(output, "[%s] [%s] endpoint %s%s %s%s lost.\n", e.Time.Format(mod.timeFormat), tui.Green(e.Tag), tui.Red(t.IpAddress), @@ -58,127 +56,84 @@ func (mod *EventsStream) viewEndpointEvent(e session.Event) { tui.Green(t.HwAddress), tui.Dim(vend)) } else { - fmt.Fprintf(mod.output, "[%s] [%s] %s\n", + fmt.Fprintf(output, "[%s] [%s] %s\n", e.Time.Format(mod.timeFormat), tui.Green(e.Tag), t.String()) } } -func (mod *EventsStream) viewModuleEvent(e session.Event) { +func (mod *EventsStream) viewModuleEvent(output io.Writer, e session.Event) { if *mod.Session.Options.Debug { - fmt.Fprintf(mod.output, "[%s] [%s] %s\n", + fmt.Fprintf(output, "[%s] [%s] %s\n", e.Time.Format(mod.timeFormat), tui.Green(e.Tag), e.Data) } } -func (mod *EventsStream) viewSnifferEvent(e session.Event) { +func (mod *EventsStream) viewSnifferEvent(output io.Writer, e session.Event) { if strings.HasPrefix(e.Tag, "net.sniff.http.") { - mod.viewHttpEvent(e) + mod.viewHttpEvent(output, e) } else { - fmt.Fprintf(mod.output, "[%s] [%s] %s\n", + fmt.Fprintf(output, "[%s] [%s] %s\n", e.Time.Format(mod.timeFormat), tui.Green(e.Tag), e.Data.(net_sniff.SnifferEvent).Message) } } -func (mod *EventsStream) viewSynScanEvent(e session.Event) { +func (mod *EventsStream) viewSynScanEvent(output io.Writer, e session.Event) { se := e.Data.(syn_scan.SynScanEvent) - fmt.Fprintf(mod.output, "[%s] [%s] found open port %d for %s\n", + fmt.Fprintf(output, "[%s] [%s] found open port %d for %s\n", e.Time.Format(mod.timeFormat), tui.Green(e.Tag), se.Port, tui.Bold(se.Address)) } -func (mod *EventsStream) viewUpdateEvent(e session.Event) { +func (mod *EventsStream) viewUpdateEvent(output io.Writer, e session.Event) { update := e.Data.(*github.RepositoryRelease) - fmt.Fprintf(mod.output, "[%s] [%s] an update to version %s is available at %s\n", + fmt.Fprintf(output, "[%s] [%s] an update to version %s is available at %s\n", e.Time.Format(mod.timeFormat), tui.Bold(tui.Yellow(e.Tag)), tui.Bold(*update.TagName), *update.HTMLURL) } -func (mod *EventsStream) doRotation() { - if mod.output == os.Stdout { - return - } else if !mod.rotation.Enabled { - return - } - - mod.rotation.Lock() - defer mod.rotation.Unlock() - - doRotate := false - if info, err := mod.output.Stat(); err == nil { - if mod.rotation.How == "size" { - doRotate = float64(info.Size()) >= float64(mod.rotation.Period*1024*1024) - } else if mod.rotation.How == "time" { - doRotate = info.ModTime().Unix()%int64(mod.rotation.Period) == 0 - } - } - - if doRotate { - var err error - - name := fmt.Sprintf("%s-%s", mod.outputName, time.Now().Format(mod.rotation.Format)) - - if err := mod.output.Close(); err != nil { - fmt.Printf("could not close log for rotation: %s\n", err) - return - } - - if err := os.Rename(mod.outputName, name); err != nil { - fmt.Printf("could not rename %s to %s: %s\n", mod.outputName, name, err) - } else if mod.rotation.Compress { - zipName := fmt.Sprintf("%s.zip", name) - if err = zip.Files(zipName, []string{name}); err != nil { - fmt.Printf("error creating %s: %s", zipName, err) - } else if err = os.Remove(name); err != nil { - fmt.Printf("error deleting %s: %s", name, err) - } - } - - mod.output, err = os.OpenFile(mod.outputName, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) - if err != nil { - fmt.Printf("could not open %s: %s", mod.outputName, err) - } - } -} - -func (mod *EventsStream) View(e session.Event, refresh bool) { +func (mod *EventsStream) Render(output io.Writer, e session.Event) { var err error if err, mod.timeFormat = mod.StringParam("events.stream.time.format"); err != nil { - fmt.Fprintf(mod.output, "%v", err) + fmt.Fprintf(output, "%v", err) mod.timeFormat = "15:04:05" } if e.Tag == "sys.log" { - mod.viewLogEvent(e) + mod.viewLogEvent(output, e) } else if strings.HasPrefix(e.Tag, "endpoint.") { - mod.viewEndpointEvent(e) + mod.viewEndpointEvent(output, e) } else if strings.HasPrefix(e.Tag, "wifi.") { - mod.viewWiFiEvent(e) + mod.viewWiFiEvent(output, e) } else if strings.HasPrefix(e.Tag, "ble.") { - mod.viewBLEEvent(e) + mod.viewBLEEvent(output, e) } else if strings.HasPrefix(e.Tag, "hid.") { - mod.viewHIDEvent(e) + mod.viewHIDEvent(output, e) } else if strings.HasPrefix(e.Tag, "mod.") { - mod.viewModuleEvent(e) + mod.viewModuleEvent(output, e) } else if strings.HasPrefix(e.Tag, "net.sniff.") { - mod.viewSnifferEvent(e) + mod.viewSnifferEvent(output, e) } else if e.Tag == "syn.scan" { - mod.viewSynScanEvent(e) + mod.viewSynScanEvent(output, e) } else if e.Tag == "update.available" { - mod.viewUpdateEvent(e) + mod.viewUpdateEvent(output, e) } else { - fmt.Fprintf(mod.output, "[%s] [%s] %v\n", e.Time.Format(mod.timeFormat), tui.Green(e.Tag), e) + fmt.Fprintf(output, "[%s] [%s] %v\n", e.Time.Format(mod.timeFormat), tui.Green(e.Tag), e) } +} + +func (mod *EventsStream) View(e session.Event, refresh bool) { + mod.Render(mod.output, e) if refresh && mod.output == os.Stdout { mod.Session.Refresh() diff --git a/modules/events_stream/events_view_ble.go b/modules/events_stream/events_view_ble.go index d5fdcde8..bb790f44 100644 --- a/modules/events_stream/events_view_ble.go +++ b/modules/events_stream/events_view_ble.go @@ -4,6 +4,7 @@ package events_stream import ( "fmt" + "io" "github.com/bettercap/bettercap/network" "github.com/bettercap/bettercap/session" @@ -11,7 +12,7 @@ import ( "github.com/evilsocket/islazy/tui" ) -func (mod *EventsStream) viewBLEEvent(e session.Event) { +func (mod *EventsStream) viewBLEEvent(output io.Writer, e session.Event) { if e.Tag == "ble.device.new" { dev := e.Data.(*network.BLEDevice) name := dev.Device.Name() @@ -23,7 +24,7 @@ func (mod *EventsStream) viewBLEEvent(e session.Event) { vend = fmt.Sprintf(" (%s)", tui.Yellow(vend)) } - fmt.Fprintf(mod.output, "[%s] [%s] new BLE device%s detected as %s%s %s.\n", + fmt.Fprintf(output, "[%s] [%s] new BLE device%s detected as %s%s %s.\n", e.Time.Format(mod.timeFormat), tui.Green(e.Tag), name, @@ -41,15 +42,11 @@ func (mod *EventsStream) viewBLEEvent(e session.Event) { vend = fmt.Sprintf(" (%s)", tui.Yellow(vend)) } - fmt.Fprintf(mod.output, "[%s] [%s] BLE device%s %s%s lost.\n", + fmt.Fprintf(output, "[%s] [%s] BLE device%s %s%s lost.\n", e.Time.Format(mod.timeFormat), tui.Green(e.Tag), name, dev.Device.ID(), vend) - } /* else { - fmt.Fprintf(s.output,"[%s] [%s]\n", - e.Time.Format(mod.timeFormat), - tui.Green(e.Tag)) - } */ + } } diff --git a/modules/events_stream/events_view_ble_unsupported.go b/modules/events_stream/events_view_ble_unsupported.go index 3c0cae14..e5415a42 100644 --- a/modules/events_stream/events_view_ble_unsupported.go +++ b/modules/events_stream/events_view_ble_unsupported.go @@ -6,6 +6,6 @@ import ( "github.com/bettercap/bettercap/session" ) -func (mod *EventsStream) viewBLEEvent(e session.Event) { +func (mod *EventsStream) viewBLEEvent(output io.Writer, e session.Event) { } diff --git a/modules/events_stream/events_view_hid.go b/modules/events_stream/events_view_hid.go index ee29a7a6..9d1918e3 100644 --- a/modules/events_stream/events_view_hid.go +++ b/modules/events_stream/events_view_hid.go @@ -2,6 +2,7 @@ package events_stream import ( "fmt" + "io" "github.com/bettercap/bettercap/network" "github.com/bettercap/bettercap/session" @@ -9,16 +10,16 @@ import ( "github.com/evilsocket/islazy/tui" ) -func (mod *EventsStream) viewHIDEvent(e session.Event) { +func (mod *EventsStream) viewHIDEvent(output io.Writer, e session.Event) { dev := e.Data.(*network.HIDDevice) if e.Tag == "hid.device.new" { - fmt.Fprintf(mod.output, "[%s] [%s] new HID device %s detected on channel %s.\n", + fmt.Fprintf(output, "[%s] [%s] new HID device %s detected on channel %s.\n", e.Time.Format(mod.timeFormat), tui.Green(e.Tag), tui.Bold(dev.Address), dev.Channels()) } else if e.Tag == "hid.device.lost" { - fmt.Fprintf(mod.output, "[%s] [%s] HID device %s lost.\n", + fmt.Fprintf(output, "[%s] [%s] HID device %s lost.\n", e.Time.Format(mod.timeFormat), tui.Green(e.Tag), tui.Red(dev.Address)) diff --git a/modules/events_stream/events_view_http.go b/modules/events_stream/events_view_http.go index 6d08c84a..ad119392 100644 --- a/modules/events_stream/events_view_http.go +++ b/modules/events_stream/events_view_http.go @@ -6,6 +6,7 @@ import ( "encoding/hex" "encoding/json" "fmt" + "io" "net/url" "regexp" "strings" @@ -128,11 +129,11 @@ func (mod *EventsStream) dumpRaw(body []byte) string { return "\n" + hex.Dump(body) + "\n" } -func (mod *EventsStream) viewHttpRequest(e session.Event) { +func (mod *EventsStream) viewHttpRequest(output io.Writer, e session.Event) { se := e.Data.(net_sniff.SnifferEvent) req := se.Data.(net_sniff.HTTPRequest) - fmt.Fprintf(mod.output, "[%s] [%s] %s\n", + fmt.Fprintf(output, "[%s] [%s] %s\n", e.Time.Format(mod.timeFormat), tui.Green(e.Tag), se.Message) @@ -166,15 +167,15 @@ func (mod *EventsStream) viewHttpRequest(e session.Event) { } } - fmt.Fprintf(mod.output, "\n%s\n", dump) + fmt.Fprintf(output, "\n%s\n", dump) } } -func (mod *EventsStream) viewHttpResponse(e session.Event) { +func (mod *EventsStream) viewHttpResponse(output io.Writer, e session.Event) { se := e.Data.(net_sniff.SnifferEvent) res := se.Data.(net_sniff.HTTPResponse) - fmt.Fprintf(mod.output, "[%s] [%s] %s\n", + fmt.Fprintf(output, "[%s] [%s] %s\n", e.Time.Format(mod.timeFormat), tui.Green(e.Tag), se.Message) @@ -198,14 +199,14 @@ func (mod *EventsStream) viewHttpResponse(e session.Event) { } } - fmt.Fprintf(mod.output, "\n%s\n", dump) + fmt.Fprintf(output, "\n%s\n", dump) } } -func (mod *EventsStream) viewHttpEvent(e session.Event) { +func (mod *EventsStream) viewHttpEvent(output io.Writer, e session.Event) { if e.Tag == "net.sniff.http.request" { - mod.viewHttpRequest(e) + mod.viewHttpRequest(output, e) } else if e.Tag == "net.sniff.http.response" { - mod.viewHttpResponse(e) + mod.viewHttpResponse(output, e) } } diff --git a/modules/events_stream/events_view_wifi.go b/modules/events_stream/events_view_wifi.go index ba072454..881c78eb 100644 --- a/modules/events_stream/events_view_wifi.go +++ b/modules/events_stream/events_view_wifi.go @@ -3,6 +3,7 @@ package events_stream import ( "fmt" "github.com/bettercap/bettercap/modules/wifi" + "io" "strings" "github.com/bettercap/bettercap/network" @@ -11,7 +12,7 @@ import ( "github.com/evilsocket/islazy/tui" ) -func (mod *EventsStream) viewWiFiApEvent(e session.Event) { +func (mod *EventsStream) viewWiFiApEvent(output io.Writer, e session.Event) { ap := e.Data.(*network.AccessPoint) vend := "" if ap.Vendor != "" { @@ -23,7 +24,7 @@ func (mod *EventsStream) viewWiFiApEvent(e session.Event) { } if e.Tag == "wifi.ap.new" { - fmt.Fprintf(mod.output, "[%s] [%s] wifi access point %s%s detected as %s%s.\n", + fmt.Fprintf(output, "[%s] [%s] wifi access point %s%s detected as %s%s.\n", e.Time.Format(mod.timeFormat), tui.Green(e.Tag), tui.Bold(ap.ESSID()), @@ -31,20 +32,20 @@ func (mod *EventsStream) viewWiFiApEvent(e session.Event) { tui.Green(ap.BSSID()), tui.Dim(vend)) } else if e.Tag == "wifi.ap.lost" { - fmt.Fprintf(mod.output, "[%s] [%s] wifi access point %s (%s) lost.\n", + fmt.Fprintf(output, "[%s] [%s] wifi access point %s (%s) lost.\n", e.Time.Format(mod.timeFormat), tui.Green(e.Tag), tui.Red(ap.ESSID()), ap.BSSID()) } else { - fmt.Fprintf(mod.output, "[%s] [%s] %s\n", + fmt.Fprintf(output, "[%s] [%s] %s\n", e.Time.Format(mod.timeFormat), tui.Green(e.Tag), ap.String()) } } -func (mod *EventsStream) viewWiFiClientProbeEvent(e session.Event) { +func (mod *EventsStream) viewWiFiClientProbeEvent(output io.Writer, e session.Event) { probe := e.Data.(wifi.ProbeEvent) desc := "" if probe.FromAlias != "" { @@ -57,7 +58,7 @@ func (mod *EventsStream) viewWiFiClientProbeEvent(e session.Event) { rssi = fmt.Sprintf(" (%d dBm)", probe.RSSI) } - fmt.Fprintf(mod.output, "[%s] [%s] station %s%s is probing for SSID %s%s\n", + fmt.Fprintf(output, "[%s] [%s] station %s%s is probing for SSID %s%s\n", e.Time.Format(mod.timeFormat), tui.Green(e.Tag), probe.FromAddr, @@ -66,7 +67,7 @@ func (mod *EventsStream) viewWiFiClientProbeEvent(e session.Event) { tui.Yellow(rssi)) } -func (mod *EventsStream) viewWiFiHandshakeEvent(e session.Event) { +func (mod *EventsStream) viewWiFiHandshakeEvent(output io.Writer, e session.Event) { hand := e.Data.(wifi.HandshakeEvent) from := hand.Station @@ -86,7 +87,7 @@ func (mod *EventsStream) viewWiFiHandshakeEvent(e session.Event) { what += " (half)" } - fmt.Fprintf(mod.output, "[%s] [%s] captured %s -> %s %s to %s\n", + fmt.Fprintf(output, "[%s] [%s] captured %s -> %s %s to %s\n", e.Time.Format(mod.timeFormat), tui.Green(e.Tag), from, @@ -95,20 +96,20 @@ func (mod *EventsStream) viewWiFiHandshakeEvent(e session.Event) { hand.File) } -func (mod *EventsStream) viewWiFiClientEvent(e session.Event) { +func (mod *EventsStream) viewWiFiClientEvent(output io.Writer, e session.Event) { ce := e.Data.(wifi.ClientEvent) ce.Client.Alias = mod.Session.Lan.GetAlias(ce.Client.BSSID()) if e.Tag == "wifi.client.new" { - fmt.Fprintf(mod.output, "[%s] [%s] new station %s detected for %s (%s)\n", + fmt.Fprintf(output, "[%s] [%s] new station %s detected for %s (%s)\n", e.Time.Format(mod.timeFormat), tui.Green(e.Tag), ce.Client.String(), tui.Bold(ce.AP.ESSID()), tui.Dim(ce.AP.BSSID())) } else if e.Tag == "wifi.client.lost" { - fmt.Fprintf(mod.output, "[%s] [%s] station %s disconnected from %s (%s)\n", + fmt.Fprintf(output, "[%s] [%s] station %s disconnected from %s (%s)\n", e.Time.Format(mod.timeFormat), tui.Green(e.Tag), ce.Client.String(), @@ -117,16 +118,16 @@ func (mod *EventsStream) viewWiFiClientEvent(e session.Event) { } } -func (mod *EventsStream) viewWiFiEvent(e session.Event) { +func (mod *EventsStream) viewWiFiEvent(output io.Writer, e session.Event) { if strings.HasPrefix(e.Tag, "wifi.ap.") { - mod.viewWiFiApEvent(e) + mod.viewWiFiApEvent(output, e) } else if e.Tag == "wifi.client.probe" { - mod.viewWiFiClientProbeEvent(e) + mod.viewWiFiClientProbeEvent(output, e) } else if e.Tag == "wifi.client.handshake" { - mod.viewWiFiHandshakeEvent(e) + mod.viewWiFiHandshakeEvent(output, e) } else if e.Tag == "wifi.client.new" || e.Tag == "wifi.client.lost" { - mod.viewWiFiClientEvent(e) + mod.viewWiFiClientEvent(output, e) } else { - fmt.Fprintf(mod.output, "[%s] [%s] %v\n", e.Time.Format(mod.timeFormat), tui.Green(e.Tag), e) + fmt.Fprintf(output, "[%s] [%s] %v\n", e.Time.Format(mod.timeFormat), tui.Green(e.Tag), e) } } diff --git a/modules/gps/gps.go b/modules/gps/gps.go index 542b5375..c4c757aa 100644 --- a/modules/gps/gps.go +++ b/modules/gps/gps.go @@ -122,7 +122,7 @@ func (mod *GPS) readLine() (line string, err error) { } func (mod *GPS) Show() error { - fmt.Printf("latitude:%f longitude:%f quality:%s satellites:%d altitude:%f\n", + mod.Printf("latitude:%f longitude:%f quality:%s satellites:%d altitude:%f\n", mod.Session.GPS.Latitude, mod.Session.GPS.Longitude, mod.Session.GPS.FixQuality, diff --git a/modules/hid/hid_show.go b/modules/hid/hid_show.go index 3418cbb4..f083eb90 100644 --- a/modules/hid/hid_show.go +++ b/modules/hid/hid_show.go @@ -1,8 +1,6 @@ package hid import ( - "fmt" - "os" "sort" "time" @@ -109,12 +107,12 @@ func (mod *HIDRecon) Show() (err error) { rows = append(rows, mod.getRow(dev)) } - tui.Table(os.Stdout, mod.colNames(), rows) + tui.Table(mod.Session.Events.Stdout, mod.colNames(), rows) if mod.sniffAddrRaw == nil { - fmt.Printf("\nchannel:%d\n\n", mod.channel) + mod.Printf("\nchannel:%d\n\n", mod.channel) } else { - fmt.Printf("\nchannel:%d sniffing:%s\n\n", mod.channel, tui.Red(mod.sniffAddr)) + mod.Printf("\nchannel:%d sniffing:%s\n\n", mod.channel, tui.Red(mod.sniffAddr)) } if len(rows) > 0 { diff --git a/modules/modules.go b/modules/modules.go index 3d63b6db..4b66890a 100644 --- a/modules/modules.go +++ b/modules/modules.go @@ -5,6 +5,7 @@ import ( "github.com/bettercap/bettercap/modules/api_rest" "github.com/bettercap/bettercap/modules/arp_spoof" "github.com/bettercap/bettercap/modules/ble" + "github.com/bettercap/bettercap/modules/c2" "github.com/bettercap/bettercap/modules/caplets" "github.com/bettercap/bettercap/modules/dhcp6_spoof" "github.com/bettercap/bettercap/modules/dns_spoof" @@ -59,6 +60,7 @@ func LoadModules(sess *session.Session) { sess.Register(wifi.NewWiFiModule(sess)) sess.Register(wol.NewWOL(sess)) sess.Register(hid.NewHIDRecon(sess)) + sess.Register(c2.NewC2(sess)) sess.Register(caplets.NewCapletsModule(sess)) sess.Register(update.NewUpdateModule(sess)) diff --git a/modules/net_recon/net_show.go b/modules/net_recon/net_show.go index da4bcd34..c3dd9874 100644 --- a/modules/net_recon/net_show.go +++ b/modules/net_recon/net_show.go @@ -2,7 +2,6 @@ package net_recon import ( "fmt" - "os" "sort" "strings" "time" @@ -223,7 +222,7 @@ func (mod *Discovery) showStatusBar() { parts = append(parts, fmt.Sprintf("%d errs", nErrors)) } - fmt.Printf("\n%s\n\n", strings.Join(parts, " / ")) + mod.Printf("\n%s\n\n", strings.Join(parts, " / ")) } func (mod *Discovery) Show(arg string) (err error) { @@ -263,7 +262,7 @@ func (mod *Discovery) Show(arg string) (err error) { } } - tui.Table(os.Stdout, colNames, rows) + tui.Table(mod.Session.Events.Stdout, colNames, rows) mod.showStatusBar() @@ -305,7 +304,7 @@ func (mod *Discovery) showMeta(arg string) (err error) { } any = true - tui.Table(os.Stdout, colNames, rows) + tui.Table(mod.Session.Events.Stdout, colNames, rows) } } diff --git a/modules/wifi/wifi_show.go b/modules/wifi/wifi_show.go index 2206361d..9b9127cd 100644 --- a/modules/wifi/wifi_show.go +++ b/modules/wifi/wifi_show.go @@ -2,7 +2,6 @@ package wifi import ( "fmt" - "os" "sort" "strconv" "strings" @@ -265,9 +264,9 @@ func (mod *WiFiModule) colNames(nrows int) []string { } else { columns = []string{"RSSI", "BSSID", "Ch", "Sent", "Recvd", "Seen"} } - fmt.Printf("\n%s clients:\n", mod.ap.HwAddress) + mod.Printf("\n%s clients:\n", mod.ap.HwAddress) } else { - fmt.Printf("\nNo authenticated clients detected for %s.\n", mod.ap.HwAddress) + mod.Printf("\nNo authenticated clients detected for %s.\n", mod.ap.HwAddress) } if columns != nil { @@ -312,7 +311,7 @@ func (mod *WiFiModule) showStatusBar() { parts = append(parts, fmt.Sprintf("%d handshakes", nHandshakes)) } - fmt.Printf("\n%s\n\n", strings.Join(parts, " / ")) + mod.Printf("\n%s\n\n", strings.Join(parts, " / ")) } func (mod *WiFiModule) Show() (err error) { @@ -337,7 +336,7 @@ func (mod *WiFiModule) Show() (err error) { } nrows := len(rows) if nrows > 0 { - tui.Table(os.Stdout, mod.colNames(nrows), rows) + tui.Table(mod.Session.Events.Stdout, mod.colNames(nrows), rows) } mod.showStatusBar() @@ -396,7 +395,7 @@ func (mod *WiFiModule) ShowWPS(bssid string) (err error) { }) } - tui.Table(os.Stdout, colNames, rows) + tui.Table(mod.Session.Events.Stdout, colNames, rows) } return nil diff --git a/session/events.go b/session/events.go index 21f973f0..c889407b 100644 --- a/session/events.go +++ b/session/events.go @@ -37,6 +37,19 @@ func (e Event) Label() string { return color + label + tui.RESET } +type EventBus <-chan Event + +type PrintCallback func(format string, args ...interface{}) + +type PrintWriter struct { + pool *EventPool +} + +func (w PrintWriter) Write(p []byte) (n int, err error) { + w.pool.Printf("%s", string(p)) + return len(p), nil +} + type EventPool struct { *sync.Mutex @@ -44,19 +57,35 @@ type EventPool struct { silent bool events []Event listeners []chan Event + printLock sync.Mutex + printCbs []PrintCallback + Stdout PrintWriter } func NewEventPool(debug bool, silent bool) *EventPool { - return &EventPool{ + pool := &EventPool{ Mutex: &sync.Mutex{}, debug: debug, silent: silent, events: make([]Event, 0), listeners: make([]chan Event, 0), + printCbs: make([]PrintCallback, 0), } + + pool.Stdout = PrintWriter{ + pool: pool, + } + + return pool } -func (p *EventPool) Listen() <-chan Event { +func (p *EventPool) OnPrint(cb PrintCallback) { + p.printLock.Lock() + defer p.printLock.Unlock() + p.printCbs = append(p.printCbs, cb) +} + +func (p *EventPool) Listen() EventBus { p.Lock() defer p.Unlock() l := make(chan Event) @@ -76,7 +105,7 @@ func (p *EventPool) Listen() <-chan Event { return l } -func (p *EventPool) Unlisten(listener <-chan Event) { +func (p *EventPool) Unlisten(listener EventBus) { p.Lock() defer p.Unlock() @@ -123,6 +152,16 @@ func (p *EventPool) Add(tag string, data interface{}) { } } +func (p *EventPool) Printf(format string, a ...interface{}) { + p.printLock.Lock() + defer p.printLock.Unlock() + + for _, cb := range p.printCbs { + cb(format, a...) + } + fmt.Printf(format, a...) +} + func (p *EventPool) Log(level log.Verbosity, format string, args ...interface{}) { if level == log.DEBUG && !p.debug { return diff --git a/session/module.go b/session/module.go index 857a90a9..343e6909 100644 --- a/session/module.go +++ b/session/module.go @@ -132,6 +132,10 @@ func (m *SessionModule) Fatal(format string, args ...interface{}) { m.Session.Events.Log(log.FATAL, m.tag+format, args...) } +func (m *SessionModule) Printf(format string, a ...interface{}) { + m.Session.Events.Printf(format, a...) +} + func (m *SessionModule) Requires(modName string) { m.requires = append(m.requires, modName) } diff --git a/session/session_core_handlers.go b/session/session_core_handlers.go index 51f40be0..86a6d678 100644 --- a/session/session_core_handlers.go +++ b/session/session_core_handlers.go @@ -33,10 +33,10 @@ func (s *Session) generalHelp() { pad := "%" + strconv.Itoa(maxLen) + "s" for _, h := range s.CoreHandlers { - fmt.Printf(" "+tui.Yellow(pad)+" : %s\n", h.Name, h.Description) + s.Events.Printf(" "+tui.Yellow(pad)+" : %s\n", h.Name, h.Description) } - fmt.Println(tui.Bold("\nModules\n")) + s.Events.Printf("%s\n", tui.Bold("\nModules\n")) maxLen = 0 for _, m := range s.Modules { @@ -54,7 +54,7 @@ func (s *Session) generalHelp() { } else { status = tui.Red("not running") } - fmt.Printf(" "+tui.Yellow(pad)+" > %s\n", m.Name(), status) + s.Events.Printf(" "+tui.Yellow(pad)+" > %s\n", m.Name(), status) } fmt.Println() @@ -73,7 +73,7 @@ func (s *Session) moduleHelp(filter string) error { } else { status = tui.Red("not running") } - fmt.Printf("%s (%s): %s\n\n", tui.Yellow(m.Name()), status, tui.Dim(m.Description())) + s.Events.Printf("%s (%s): %s\n\n", tui.Yellow(m.Name()), status, tui.Dim(m.Description())) maxLen := 0 handlers := m.Handlers() @@ -85,7 +85,7 @@ func (s *Session) moduleHelp(filter string) error { } for _, h := range handlers { - fmt.Print(h.Help(maxLen)) + s.Events.Printf("%s", h.Help(maxLen)) } fmt.Println() @@ -105,9 +105,9 @@ func (s *Session) moduleHelp(filter string) error { return v[i].Name < v[j].Name }) - fmt.Print(" Parameters\n\n") + s.Events.Printf(" Parameters\n\n") for _, p := range v { - fmt.Print(p.Help(maxLen)) + s.Events.Printf("%s", p.Help(maxLen)) } fmt.Println() } @@ -138,13 +138,13 @@ func (s *Session) activeHandler(args []string, sess *Session) error { continue } - fmt.Printf("%s (%s)\n", tui.Bold(m.Name()), tui.Dim(m.Description())) + s.Events.Printf("%s (%s)\n", tui.Bold(m.Name()), tui.Dim(m.Description())) params := m.Parameters() if len(params) > 0 { fmt.Println() for _, p := range params { _, val := s.Env.Get(p.Name) - fmt.Printf(" %s : %s\n", tui.Yellow(p.Name), val) + s.Events.Printf(" %s : %s\n", tui.Yellow(p.Name), val) } } @@ -206,13 +206,13 @@ func (s *Session) getHandler(args []string, sess *Session) error { prev_ns = ns } - fmt.Printf(" %"+strconv.Itoa(padding)+"s: '%s'\n", k, s.Env.Data[k]) + s.Events.Printf(" %"+strconv.Itoa(padding)+"s: '%s'\n", k, s.Env.Data[k]) } } fmt.Println() } else if found, value := s.Env.Get(key); found { fmt.Println() - fmt.Printf(" %s: '%s'\n", key, value) + s.Events.Printf(" %s: '%s'\n", key, value) fmt.Println() } else { return fmt.Errorf("%s not found", key) @@ -269,7 +269,7 @@ func (s *Session) includeHandler(args []string, sess *Session) error { func (s *Session) shHandler(args []string, sess *Session) error { out, err := core.Shell(args[0]) if err == nil { - fmt.Printf("%s\n", out) + s.Events.Printf("%s\n", out) } return err }