mirror of
https://github.com/bettercap/bettercap
synced 2025-08-14 02:36:57 -07:00
new: added events.on (and other related commands) to trigger specific actions when an events happens
This commit is contained in:
parent
78c341c2b3
commit
1492bf5e40
24 changed files with 4475 additions and 0 deletions
|
@ -29,6 +29,7 @@ type EventsStream struct {
|
|||
output *os.File
|
||||
rotation rotation
|
||||
ignoreList *IgnoreList
|
||||
triggerList *TriggerList
|
||||
waitFor string
|
||||
waitChan chan *session.Event
|
||||
eventListener <-chan session.Event
|
||||
|
@ -45,6 +46,7 @@ func NewEventsStream(s *session.Session) *EventsStream {
|
|||
waitChan: make(chan *session.Event),
|
||||
waitFor: "",
|
||||
ignoreList: NewIgnoreList(),
|
||||
triggerList: NewTriggerList(),
|
||||
}
|
||||
|
||||
mod.AddHandler(session.NewModuleHandler("events.stream on", "",
|
||||
|
@ -70,6 +72,38 @@ func NewEventsStream(s *session.Session) *EventsStream {
|
|||
return mod.Show(limit)
|
||||
}))
|
||||
|
||||
on := session.NewModuleHandler("events.on TAG COMMANDS", `events\.on ([^\s]+) (.+)`,
|
||||
"Run COMMANDS when an event with the specified TAG is triggered.",
|
||||
func(args []string) error {
|
||||
return mod.addTrigger(args[0], args[1])
|
||||
})
|
||||
|
||||
on.Complete("events.on", s.EventsCompleter)
|
||||
|
||||
mod.AddHandler(on)
|
||||
|
||||
mod.AddHandler(session.NewModuleHandler("events.triggers", "",
|
||||
"Show the list of event triggers created by the events.on command.",
|
||||
func(args []string) error {
|
||||
return mod.showTriggers()
|
||||
}))
|
||||
|
||||
onClear := session.NewModuleHandler("events.trigger.delete TRIGGER_ID", `events\.trigger\.delete ([^\s]+)`,
|
||||
"Remove an event trigger given its TRIGGER_ID (use events.triggers to see the list of triggers).",
|
||||
func(args []string) error {
|
||||
return mod.clearTrigger(args[0])
|
||||
})
|
||||
|
||||
onClear.Complete("events.trigger.delete", mod.triggerList.Completer)
|
||||
|
||||
mod.AddHandler(onClear)
|
||||
|
||||
mod.AddHandler(session.NewModuleHandler("events.triggers.clear", "",
|
||||
"Remove all event triggers (use events.triggers to see the list of triggers).",
|
||||
func(args []string) error {
|
||||
return mod.clearTrigger("")
|
||||
}))
|
||||
|
||||
mod.AddHandler(session.NewModuleHandler("events.waitfor TAG TIMEOUT?", `events.waitfor ([^\s]+)([\s\d]*)`,
|
||||
"Wait for an event with the given tag either forever or for a timeout in seconds.",
|
||||
func(args []string) error {
|
||||
|
@ -242,6 +276,10 @@ func (mod *EventsStream) Start() error {
|
|||
mod.View(e, true)
|
||||
}
|
||||
|
||||
// this could generate sys.log events and lock the whole
|
||||
// events.stream, make it async
|
||||
go mod.dispatchTriggers(e)
|
||||
|
||||
case <-mod.quit:
|
||||
return
|
||||
}
|
||||
|
|
62
modules/events_stream/events_triggers.go
Normal file
62
modules/events_stream/events_triggers.go
Normal file
|
@ -0,0 +1,62 @@
|
|||
package events_stream
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/bettercap/bettercap/session"
|
||||
|
||||
"github.com/evilsocket/islazy/tui"
|
||||
)
|
||||
|
||||
func (mod *EventsStream) addTrigger(tag string, command string) error {
|
||||
if err, id := mod.triggerList.Add(tag, command); err != nil {
|
||||
return err
|
||||
} else {
|
||||
mod.Info("trigger for event %s added with identifier '%s'", tui.Green(tag), tui.Bold(id))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (mod *EventsStream) clearTrigger(id string) error {
|
||||
if err := mod.triggerList.Del(id); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (mod *EventsStream) showTriggers() error {
|
||||
colNames := []string{
|
||||
"ID",
|
||||
"Event",
|
||||
"Action",
|
||||
}
|
||||
rows := [][]string{}
|
||||
|
||||
mod.triggerList.Each(func(id string, t Trigger) {
|
||||
rows = append(rows, []string{
|
||||
tui.Bold(id),
|
||||
tui.Green(t.For),
|
||||
t.Action,
|
||||
})
|
||||
})
|
||||
|
||||
if len(rows) > 0 {
|
||||
tui.Table(os.Stdout, colNames, rows)
|
||||
mod.Session.Refresh()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (mod *EventsStream) dispatchTriggers(e session.Event) {
|
||||
if id, cmds, err, found := mod.triggerList.Dispatch(e); err != nil {
|
||||
mod.Error("error while dispatching event %s: %v", e.Tag, err)
|
||||
} else if found {
|
||||
mod.Debug("running trigger %s (cmds:'%s') for event %v", id, cmds, e)
|
||||
for _, cmd := range session.ParseCommands(cmds) {
|
||||
if err := mod.Session.Run(cmd); err != nil {
|
||||
mod.Error("%s", err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
136
modules/events_stream/trigger_list.go
Normal file
136
modules/events_stream/trigger_list.go
Normal file
|
@ -0,0 +1,136 @@
|
|||
package events_stream
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/bettercap/bettercap/session"
|
||||
|
||||
"github.com/antchfx/jsonquery"
|
||||
"github.com/evilsocket/islazy/str"
|
||||
"github.com/evilsocket/islazy/tui"
|
||||
)
|
||||
|
||||
var reQueryCapture = regexp.MustCompile(`{{([^}]+)}}`)
|
||||
|
||||
type Trigger struct {
|
||||
For string
|
||||
Action string
|
||||
}
|
||||
|
||||
type TriggerList struct {
|
||||
sync.Mutex
|
||||
triggers map[string]Trigger
|
||||
}
|
||||
|
||||
func NewTriggerList() *TriggerList {
|
||||
return &TriggerList{
|
||||
triggers: make(map[string]Trigger),
|
||||
}
|
||||
}
|
||||
|
||||
func (l *TriggerList) Add(tag string, command string) (error, string) {
|
||||
l.Lock()
|
||||
defer l.Unlock()
|
||||
|
||||
idNum := 0
|
||||
command = str.Trim(command)
|
||||
|
||||
for id, t := range l.triggers {
|
||||
if t.For == tag {
|
||||
if t.Action == command {
|
||||
return fmt.Errorf("duplicate: trigger '%s' found for action '%s'", tui.Bold(id), command), ""
|
||||
}
|
||||
idNum++
|
||||
}
|
||||
}
|
||||
|
||||
id := fmt.Sprintf("%s-%d", tag, idNum)
|
||||
l.triggers[id] = Trigger{
|
||||
For: tag,
|
||||
Action: command,
|
||||
}
|
||||
|
||||
return nil, id
|
||||
}
|
||||
|
||||
func (l *TriggerList) Del(id string) (err error) {
|
||||
l.Lock()
|
||||
defer l.Unlock()
|
||||
if _, found := l.triggers[id]; found {
|
||||
delete(l.triggers, id)
|
||||
} else {
|
||||
err = fmt.Errorf("trigger '%s' not found", tui.Bold(id))
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (l *TriggerList) Each(cb func(id string, t Trigger)) {
|
||||
l.Lock()
|
||||
defer l.Unlock()
|
||||
for id, t := range l.triggers {
|
||||
cb(id, t)
|
||||
}
|
||||
}
|
||||
|
||||
func (l *TriggerList) Completer(prefix string) []string {
|
||||
ids := []string{}
|
||||
l.Each(func(id string, t Trigger) {
|
||||
if prefix == "" || strings.HasPrefix(id, prefix) {
|
||||
ids = append(ids, id)
|
||||
}
|
||||
})
|
||||
return ids
|
||||
}
|
||||
|
||||
func (l *TriggerList) Dispatch(e session.Event) (ident string, cmd string, err error, found bool) {
|
||||
l.Lock()
|
||||
defer l.Unlock()
|
||||
|
||||
for id, t := range l.triggers {
|
||||
if e.Tag == t.For {
|
||||
found = true
|
||||
ident = id
|
||||
// this is ugly but it's also the only way to allow
|
||||
// the user to do this easily - since each event Data
|
||||
// field is an interface and type casting is not possible
|
||||
// via golang default text/template system, we transform
|
||||
// the field to JSON, parse it again and then allow the
|
||||
// user to access it in the command via JSON-Query, example:
|
||||
//
|
||||
// events.on wifi.client.new "wifi.deauth {{Client\mac}}"
|
||||
buf := ([]byte)(nil)
|
||||
doc := (*jsonquery.Node)(nil)
|
||||
cmd = t.Action
|
||||
// parse each {EXPR}
|
||||
for _, m := range reQueryCapture.FindAllString(t.Action, -1) {
|
||||
// parse the event Data field as a JSON objects once
|
||||
if doc == nil {
|
||||
if buf, err = json.Marshal(e.Data); err != nil {
|
||||
err = fmt.Errorf("error while encoding event for trigger %s: %v", tui.Bold(id), err)
|
||||
return
|
||||
} else if doc, err = jsonquery.Parse(strings.NewReader(string(buf))); err != nil {
|
||||
err = fmt.Errorf("error while parsing event for trigger %s: %v", tui.Bold(id), err)
|
||||
return
|
||||
}
|
||||
}
|
||||
// {EXPR} -> EXPR
|
||||
expr := strings.Trim(m, "{}")
|
||||
// use EXPR as a JSON query
|
||||
if node := jsonquery.FindOne(doc, expr); node != nil {
|
||||
cmd = strings.Replace(cmd, m, node.InnerText(), -1)
|
||||
} else {
|
||||
err = fmt.Errorf("error while parsing expressionfor trigger %s: '%s' doesn't resolve any object", tui.Bold(id), expr, expr, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue