From f213d0d0ca5d0c790bf5890ad47da93efe058232 Mon Sep 17 00:00:00 2001 From: evilsocket Date: Sat, 25 Aug 2018 11:31:25 +0200 Subject: [PATCH] fix: working directory is temporarily switched when running a caplet in order to properly load/include files relative to the caplet itself --- session/caplet.go | 58 ++++++---- session/session.go | 237 +-------------------------------------- session/session_parse.go | 84 ++++++++++++++ session/session_setup.go | 140 +++++++++++++++++++++++ 4 files changed, 265 insertions(+), 254 deletions(-) create mode 100644 session/session_parse.go create mode 100644 session/session_setup.go diff --git a/session/caplet.go b/session/caplet.go index 29619b14..9b99c769 100644 --- a/session/caplet.go +++ b/session/caplet.go @@ -3,7 +3,6 @@ package session import ( "bufio" "fmt" - "io/ioutil" "os" "path/filepath" "strings" @@ -22,7 +21,6 @@ type Caplet struct { } var ( - CapletsTree = make(map[string][]string) CapletLoadPaths = []string{ "./caplets/", "/usr/share/bettercap/caplets/", @@ -32,20 +30,6 @@ var ( cacheLock = sync.Mutex{} ) -func buildCapletsTree(path string, prefix string) { - files, _ := ioutil.ReadDir(path) - for _, file := range files { - filename := file.Name() - if strings.HasSuffix(filename, CapletSuffix) { - base := strings.TrimPrefix(path, prefix) - name := strings.Replace(filename, CapletSuffix, "", -1) - CapletsTree[base+name] = []string{} - } else if file.IsDir() { - buildCapletsTree(filepath.Join(path, filename)+"/", prefix) - } - } -} - func init() { for _, path := range core.SepSplit(core.Trim(os.Getenv("CAPSPATH")), ":") { if path = core.Trim(path); len(path) > 0 { @@ -56,10 +40,6 @@ func init() { for i, path := range CapletLoadPaths { CapletLoadPaths[i], _ = filepath.Abs(path) } - - for _, path := range CapletLoadPaths { - buildCapletsTree(path, path) - } } func LoadCaplet(name string) (error, *Caplet) { @@ -133,3 +113,41 @@ func parseCapletCommand(line string) (is bool, caplet *Caplet, argv []string) { return false, nil, nil } + +func (cap *Caplet) Eval(s *Session, argv []string) error { + // the caplet might include other files (include directive, proxy modules, etc), + // temporarily change the working directory + cwd, err := os.Getwd() + if err != nil { + return fmt.Errorf("error while getting current working directory: %v", err) + } + + capPath := filepath.Dir(cap.Path) + if err := os.Chdir(capPath); err != nil { + return fmt.Errorf("error while changing current working directory: %v", err) + } + + defer func() { + if err := os.Chdir(cwd); err != nil { + s.Events.Log(core.ERROR, "error while restoring working directory: %v", err) + } + }() + + if argv == nil { + argv = []string{} + } + + for _, line := range cap.Code { + // replace $0 with argv[0], $1 with argv[1] and so on + for i, arg := range argv { + what := fmt.Sprintf("$%d", i) + line = strings.Replace(line, what, arg, -1) + } + + if err = s.Run(line + "\n"); err != nil { + return err + } + } + + return nil +} diff --git a/session/session.go b/session/session.go index 65776edb..888ecd09 100644 --- a/session/session.go +++ b/session/session.go @@ -5,13 +5,10 @@ import ( "fmt" "net" "os" - "os/signal" "regexp" "runtime" "runtime/pprof" "sort" - "strings" - "syscall" "time" "github.com/bettercap/readline" @@ -65,70 +62,6 @@ type Session struct { UnkCmdCallback UnknownCommandCallback `json:"-"` } -func ParseCommands(line string) []string { - args := []string{} - buf := "" - - singleQuoted := false - doubleQuoted := false - finish := false - - for _, c := range line { - switch c { - case ';': - if !singleQuoted && !doubleQuoted { - finish = true - } else { - buf += string(c) - } - - case '"': - if doubleQuoted { - // finish of quote - doubleQuoted = false - } else if singleQuoted { - // quote initiated with ', so we ignore it - buf += string(c) - } else { - // quote init here - doubleQuoted = true - } - - case '\'': - if singleQuoted { - singleQuoted = false - } else if doubleQuoted { - buf += string(c) - } else { - singleQuoted = true - } - - default: - buf += string(c) - } - - if finish { - args = append(args, buf) - finish = false - buf = "" - } - } - - if len(buf) > 0 { - args = append(args, buf) - } - - cmds := make([]string, 0) - for _, cmd := range args { - cmd = core.Trim(cmd) - if cmd != "" || (len(cmd) > 0 && cmd[0] != '#') { - cmds = append(cmds, cmd) - } - } - - return cmds -} - func New() (*Session, error) { var err error @@ -182,70 +115,6 @@ func (s *Session) Module(name string) (err error, mod Module) { return fmt.Errorf("Module %s not found", name), mod } -func (s *Session) setupReadline() error { - var err error - - pcompleters := make([]readline.PrefixCompleterInterface, 0) - for _, h := range s.CoreHandlers { - if h.Completer == nil { - pcompleters = append(pcompleters, readline.PcItem(h.Name)) - } else { - pcompleters = append(pcompleters, h.Completer) - } - } - - tree := make(map[string][]string) - for _, m := range s.Modules { - for _, h := range m.Handlers() { - parts := strings.Split(h.Name, " ") - name := parts[0] - - if _, found := tree[name]; !found { - tree[name] = []string{} - } - - var appendedOption = strings.Join(parts[1:], " ") - - if len(appendedOption) > 0 && !containsCapitals(appendedOption) { - tree[name] = append(tree[name], appendedOption) - } - } - } - - for root, subElems := range CapletsTree { - item := readline.PcItem(root) - item.Children = []readline.PrefixCompleterInterface{} - for _, child := range subElems { - item.Children = append(item.Children, readline.PcItem(child)) - } - pcompleters = append(pcompleters, item) - } - - history := "" - if !*s.Options.NoHistory { - history, _ = core.ExpandPath(HistoryFile) - } - - cfg := readline.Config{ - HistoryFile: history, - InterruptPrompt: "^C", - EOFPrompt: "exit", - AutoComplete: readline.NewPrefixCompleter(pcompleters...), - } - - s.Input, err = readline.NewEx(&cfg) - return err -} - -func containsCapitals(s string) bool { - for _, ch := range s { - if ch < 133 && ch > 101 { - return false - } - } - return true -} - func (s *Session) Close() { if *s.Options.Debug { fmt.Printf("\nStopping modules and cleaning session state ...\n") @@ -290,77 +159,6 @@ func (s *Session) Register(mod Module) error { return nil } -func (s *Session) startNetMon() { - // keep reading network events in order to add / update endpoints - go func() { - for event := range s.Queue.Activities { - if !s.Active { - return - } - - if s.IsOn("net.recon") && event.Source { - addr := event.IP.String() - mac := event.MAC.String() - - existing := s.Lan.AddIfNew(addr, mac) - if existing != nil { - existing.LastSeen = time.Now() - } - } - } - }() -} - -func (s *Session) setupSignals() { - c := make(chan os.Signal) - signal.Notify(c, os.Interrupt, syscall.SIGTERM) - go func() { - <-c - fmt.Println() - s.Events.Log(core.WARNING, "Got SIGTERM") - s.Close() - os.Exit(0) - }() -} - -func (s *Session) setupEnv() { - s.Env.Set("iface.index", fmt.Sprintf("%d", s.Interface.Index)) - s.Env.Set("iface.name", s.Interface.Name()) - s.Env.Set("iface.ipv4", s.Interface.IpAddress) - s.Env.Set("iface.ipv6", s.Interface.Ip6Address) - s.Env.Set("iface.mac", s.Interface.HwAddress) - s.Env.Set("gateway.address", s.Gateway.IpAddress) - s.Env.Set("gateway.mac", s.Gateway.HwAddress) - - if found, v := s.Env.Get(PromptVariable); !found || v == "" { - s.Env.Set(PromptVariable, DefaultPrompt) - } - - dbg := "false" - if *s.Options.Debug { - dbg = "true" - } - s.Env.WithCallback("log.debug", dbg, func(newValue string) { - newDbg := false - if newValue == "true" { - newDbg = true - } - s.Events.SetDebug(newDbg) - }) - - silent := "false" - if *s.Options.Silent { - silent = "true" - } - s.Env.WithCallback("log.silent", silent, func(newValue string) { - newSilent := false - if newValue == "true" { - newSilent = true - } - s.Events.SetSilent(newSilent) - }) -} - func (s *Session) Start() error { var err error @@ -445,19 +243,6 @@ func (s *Session) IsOn(moduleName string) bool { return false } -func (s *Session) parseEnvTokens(str string) (string, error) { - // replace all {env.something} with their values - for _, m := range reEnvVarCapture.FindAllString(str, -1) { - varName := strings.Trim(strings.Replace(m, "env.", "", -1), "{}") - if found, value := s.Env.Get(varName); found { - str = strings.Replace(str, m, value, -1) - } else { - return "", fmt.Errorf("variable '%s' is not defined", varName) - } - } - return str, nil -} - func (s *Session) Refresh() { p, _ := s.parseEnvTokens(s.Prompt.Render(s)) s.Input.SetPrompt(p) @@ -475,13 +260,7 @@ func (s *Session) RunCaplet(filename string) error { return err } - for _, line := range caplet.Code { - if err = s.Run(line + "\n"); err != nil { - return err - } - } - - return nil + return caplet.Eval(s, nil) } func (s *Session) Run(line string) error { @@ -491,6 +270,7 @@ func (s *Session) Run(line string) error { // to 'arp.spoof on' (fixes #178) line = reCmdSpaceCleaner.ReplaceAllString(line, "$1 $2") + // replace all {env.something} with their values line, err := s.parseEnvTokens(line) if err != nil { return err @@ -514,18 +294,7 @@ func (s *Session) Run(line string) error { // is it a caplet command? if parsed, caplet, argv := parseCapletCommand(line); parsed { - for _, line := range caplet.Code { - // replace $0 with argv[0], $1 with argv[1] and so on - for i, arg := range argv { - line = strings.Replace(line, fmt.Sprintf("$%d", i), arg, -1) - } - - if err = s.Run(line + "\n"); err != nil { - return err - } - } - - return nil + return caplet.Eval(s, argv) } // is it a proxy module custom command? diff --git a/session/session_parse.go b/session/session_parse.go new file mode 100644 index 00000000..f3f05f3d --- /dev/null +++ b/session/session_parse.go @@ -0,0 +1,84 @@ +package session + +import ( + "fmt" + "strings" + + "github.com/bettercap/bettercap/core" +) + +func ParseCommands(line string) []string { + args := []string{} + buf := "" + + singleQuoted := false + doubleQuoted := false + finish := false + + for _, c := range line { + switch c { + case ';': + if !singleQuoted && !doubleQuoted { + finish = true + } else { + buf += string(c) + } + + case '"': + if doubleQuoted { + // finish of quote + doubleQuoted = false + } else if singleQuoted { + // quote initiated with ', so we ignore it + buf += string(c) + } else { + // quote init here + doubleQuoted = true + } + + case '\'': + if singleQuoted { + singleQuoted = false + } else if doubleQuoted { + buf += string(c) + } else { + singleQuoted = true + } + + default: + buf += string(c) + } + + if finish { + args = append(args, buf) + finish = false + buf = "" + } + } + + if len(buf) > 0 { + args = append(args, buf) + } + + cmds := make([]string, 0) + for _, cmd := range args { + cmd = core.Trim(cmd) + if cmd != "" || (len(cmd) > 0 && cmd[0] != '#') { + cmds = append(cmds, cmd) + } + } + + return cmds +} + +func (s *Session) parseEnvTokens(str string) (string, error) { + for _, m := range reEnvVarCapture.FindAllString(str, -1) { + varName := strings.Trim(strings.Replace(m, "env.", "", -1), "{}") + if found, value := s.Env.Get(varName); found { + str = strings.Replace(str, m, value, -1) + } else { + return "", fmt.Errorf("variable '%s' is not defined", varName) + } + } + return str, nil +} diff --git a/session/session_setup.go b/session/session_setup.go new file mode 100644 index 00000000..a786aa9d --- /dev/null +++ b/session/session_setup.go @@ -0,0 +1,140 @@ +package session + +import ( + "fmt" + "os" + "os/signal" + "strings" + "syscall" + "time" + + "github.com/bettercap/readline" + + "github.com/bettercap/bettercap/core" +) + +func containsCapitals(s string) bool { + for _, ch := range s { + if ch < 133 && ch > 101 { + return false + } + } + return true +} + +func (s *Session) setupReadline() error { + var err error + + pcompleters := make([]readline.PrefixCompleterInterface, 0) + for _, h := range s.CoreHandlers { + if h.Completer == nil { + pcompleters = append(pcompleters, readline.PcItem(h.Name)) + } else { + pcompleters = append(pcompleters, h.Completer) + } + } + + tree := make(map[string][]string) + for _, m := range s.Modules { + for _, h := range m.Handlers() { + parts := strings.Split(h.Name, " ") + name := parts[0] + + if _, found := tree[name]; !found { + tree[name] = []string{} + } + + var appendedOption = strings.Join(parts[1:], " ") + + if len(appendedOption) > 0 && !containsCapitals(appendedOption) { + tree[name] = append(tree[name], appendedOption) + } + } + } + + history := "" + if !*s.Options.NoHistory { + history, _ = core.ExpandPath(HistoryFile) + } + + cfg := readline.Config{ + HistoryFile: history, + InterruptPrompt: "^C", + EOFPrompt: "exit", + AutoComplete: readline.NewPrefixCompleter(pcompleters...), + } + + s.Input, err = readline.NewEx(&cfg) + return err +} + +func (s *Session) startNetMon() { + // keep reading network events in order to add / update endpoints + go func() { + for event := range s.Queue.Activities { + if !s.Active { + return + } + + if s.IsOn("net.recon") && event.Source { + addr := event.IP.String() + mac := event.MAC.String() + + existing := s.Lan.AddIfNew(addr, mac) + if existing != nil { + existing.LastSeen = time.Now() + } + } + } + }() +} + +func (s *Session) setupSignals() { + c := make(chan os.Signal) + signal.Notify(c, os.Interrupt, syscall.SIGTERM) + go func() { + <-c + fmt.Println() + s.Events.Log(core.WARNING, "Got SIGTERM") + s.Close() + os.Exit(0) + }() +} + +func (s *Session) setupEnv() { + s.Env.Set("iface.index", fmt.Sprintf("%d", s.Interface.Index)) + s.Env.Set("iface.name", s.Interface.Name()) + s.Env.Set("iface.ipv4", s.Interface.IpAddress) + s.Env.Set("iface.ipv6", s.Interface.Ip6Address) + s.Env.Set("iface.mac", s.Interface.HwAddress) + s.Env.Set("gateway.address", s.Gateway.IpAddress) + s.Env.Set("gateway.mac", s.Gateway.HwAddress) + + if found, v := s.Env.Get(PromptVariable); !found || v == "" { + s.Env.Set(PromptVariable, DefaultPrompt) + } + + dbg := "false" + if *s.Options.Debug { + dbg = "true" + } + s.Env.WithCallback("log.debug", dbg, func(newValue string) { + newDbg := false + if newValue == "true" { + newDbg = true + } + s.Events.SetDebug(newDbg) + }) + + silent := "false" + if *s.Options.Silent { + silent = "true" + } + s.Env.WithCallback("log.silent", silent, func(newValue string) { + newSilent := false + if newValue == "true" { + newSilent = true + } + s.Events.SetSilent(newSilent) + }) +}