package events_stream import ( "encoding/json" "fmt" "regexp" "strings" "sync" "github.com/bettercap/bettercap/v2/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 { // 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}}" cmd = t.Action found = true ident = id buf := ([]byte)(nil) doc := (*jsonquery.Node)(nil) // 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: %v", tui.Bold(id), expr, err, ) return } } return } } return }