fix: working directory is temporarily switched when running a caplet in order to properly load/include files relative to the caplet itself

This commit is contained in:
evilsocket 2018-08-25 11:31:25 +02:00
parent 9a3a514f6a
commit f213d0d0ca
No known key found for this signature in database
GPG key ID: 1564D7F30393A456
4 changed files with 265 additions and 254 deletions

View file

@ -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
}

View file

@ -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?

84
session/session_parse.go Normal file
View file

@ -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
}

140
session/session_setup.go Normal file
View file

@ -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)
})
}