trigger_list.go 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  1. package events_stream
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "regexp"
  6. "strings"
  7. "sync"
  8. "github.com/bettercap/bettercap/session"
  9. "github.com/antchfx/jsonquery"
  10. "github.com/evilsocket/islazy/str"
  11. "github.com/evilsocket/islazy/tui"
  12. )
  13. var reQueryCapture = regexp.MustCompile(`{{([^}]+)}}`)
  14. type Trigger struct {
  15. For string
  16. Action string
  17. }
  18. type TriggerList struct {
  19. sync.Mutex
  20. triggers map[string]Trigger
  21. }
  22. func NewTriggerList() *TriggerList {
  23. return &TriggerList{
  24. triggers: make(map[string]Trigger),
  25. }
  26. }
  27. func (l *TriggerList) Add(tag string, command string) (error, string) {
  28. l.Lock()
  29. defer l.Unlock()
  30. idNum := 0
  31. command = str.Trim(command)
  32. for id, t := range l.triggers {
  33. if t.For == tag {
  34. if t.Action == command {
  35. return fmt.Errorf("duplicate: trigger '%s' found for action '%s'", tui.Bold(id), command), ""
  36. }
  37. idNum++
  38. }
  39. }
  40. id := fmt.Sprintf("%s-%d", tag, idNum)
  41. l.triggers[id] = Trigger{
  42. For: tag,
  43. Action: command,
  44. }
  45. return nil, id
  46. }
  47. func (l *TriggerList) Del(id string) (err error) {
  48. l.Lock()
  49. defer l.Unlock()
  50. if _, found := l.triggers[id]; found {
  51. delete(l.triggers, id)
  52. } else {
  53. err = fmt.Errorf("trigger '%s' not found", tui.Bold(id))
  54. }
  55. return err
  56. }
  57. func (l *TriggerList) Each(cb func(id string, t Trigger)) {
  58. l.Lock()
  59. defer l.Unlock()
  60. for id, t := range l.triggers {
  61. cb(id, t)
  62. }
  63. }
  64. func (l *TriggerList) Completer(prefix string) []string {
  65. ids := []string{}
  66. l.Each(func(id string, t Trigger) {
  67. if prefix == "" || strings.HasPrefix(id, prefix) {
  68. ids = append(ids, id)
  69. }
  70. })
  71. return ids
  72. }
  73. func (l *TriggerList) Dispatch(e session.Event) (ident string, cmd string, err error, found bool) {
  74. l.Lock()
  75. defer l.Unlock()
  76. for id, t := range l.triggers {
  77. if e.Tag == t.For {
  78. // this is ugly but it's also the only way to allow
  79. // the user to do this easily - since each event Data
  80. // field is an interface and type casting is not possible
  81. // via golang default text/template system, we transform
  82. // the field to JSON, parse it again and then allow the
  83. // user to access it in the command via JSON-Query, example:
  84. //
  85. // events.on wifi.client.new "wifi.deauth {{Client\mac}}"
  86. cmd = t.Action
  87. found = true
  88. ident = id
  89. buf := ([]byte)(nil)
  90. doc := (*jsonquery.Node)(nil)
  91. // parse each {EXPR}
  92. for _, m := range reQueryCapture.FindAllString(t.Action, -1) {
  93. // parse the event Data field as a JSON objects once
  94. if doc == nil {
  95. if buf, err = json.Marshal(e.Data); err != nil {
  96. err = fmt.Errorf("error while encoding event for trigger %s: %v", tui.Bold(id), err)
  97. return
  98. } else if doc, err = jsonquery.Parse(strings.NewReader(string(buf))); err != nil {
  99. err = fmt.Errorf("error while parsing event for trigger %s: %v", tui.Bold(id), err)
  100. return
  101. }
  102. }
  103. // {EXPR} -> EXPR
  104. expr := strings.Trim(m, "{}")
  105. // use EXPR as a JSON query
  106. if node := jsonquery.FindOne(doc, expr); node != nil {
  107. cmd = strings.Replace(cmd, m, node.InnerText(), -1)
  108. } else {
  109. err = fmt.Errorf(
  110. "error while parsing expressionfor trigger %s: '%s' doesn't resolve any object: %v",
  111. tui.Bold(id),
  112. expr,
  113. err,
  114. )
  115. return
  116. }
  117. }
  118. return
  119. }
  120. }
  121. return
  122. }