diff --git a/.gitignore b/.gitignore index af53adfe..086641b1 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,6 @@ *.tar.gz *.prof* pcaps -caplets build bettercap bettercap.history diff --git a/caplets/caplet.go b/caplets/caplet.go new file mode 100644 index 00000000..4ef35101 --- /dev/null +++ b/caplets/caplet.go @@ -0,0 +1,53 @@ +package caplets + +import ( + "fmt" + "os" + "path/filepath" + "strings" +) + +type Caplet struct { + Name string + Path string + Size int64 + Code []string +} + +func (cap *Caplet) Eval(argv []string, lineCb func(line string) error) 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 { + fmt.Printf("error while restoring working directory: %v\n", 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 = lineCb(line); err != nil { + return err + } + } + + return nil +} diff --git a/caplets/doc.go b/caplets/doc.go new file mode 100644 index 00000000..0a709a6d --- /dev/null +++ b/caplets/doc.go @@ -0,0 +1,2 @@ +// Package caplets contains functions to enumerate, load and execute caplets. +package caplets diff --git a/caplets/env.go b/caplets/env.go new file mode 100644 index 00000000..257ff7e6 --- /dev/null +++ b/caplets/env.go @@ -0,0 +1,32 @@ +package caplets + +import ( + "os" + "path/filepath" + + "github.com/bettercap/bettercap/core" +) + +const ( + Suffix = ".cap" + InstallPath = "/usr/local/share/bettercap/caplets/" +) + +var ( + LoadPaths = []string{ + "./caplets/", + InstallPath, + } +) + +func init() { + for _, path := range core.SepSplit(core.Trim(os.Getenv("CAPSPATH")), ":") { + if path = core.Trim(path); len(path) > 0 { + LoadPaths = append(LoadPaths, path) + } + } + + for i, path := range LoadPaths { + LoadPaths[i], _ = filepath.Abs(path) + } +} diff --git a/caplets/manager.go b/caplets/manager.go new file mode 100644 index 00000000..c094dc8d --- /dev/null +++ b/caplets/manager.go @@ -0,0 +1,93 @@ +package caplets + +import ( + "bufio" + "fmt" + "os" + "path/filepath" + "strings" + "sync" + + "github.com/bettercap/bettercap/core" +) + +var ( + cache = make(map[string]*Caplet) + cacheLock = sync.Mutex{} +) + +func List() []Caplet { + caplets := make([]Caplet, 0) + cwd, _ := filepath.Abs(".") + + for _, searchPath := range append([]string{cwd}, LoadPaths...) { + files, _ := filepath.Glob(searchPath + "/*" + Suffix) + files2, _ := filepath.Glob(searchPath + "/*/*" + Suffix) + + for _, fileName := range append(files, files2...) { + if stats, err := os.Stat(fileName); err == nil { + base := strings.Replace(fileName, searchPath+"/", "", -1) + base = strings.Replace(base, Suffix, "", -1) + + caplets = append(caplets, Caplet{ + Name: base, + Path: fileName, + Size: stats.Size(), + }) + } + } + } + + return caplets +} + +func Load(name string) (error, *Caplet) { + cacheLock.Lock() + defer cacheLock.Unlock() + + if caplet, found := cache[name]; found { + return nil, caplet + } + + names := []string{name} + if !strings.HasSuffix(name, Suffix) { + names = append(names, name+Suffix) + } + + for _, path := range LoadPaths { + if !strings.HasSuffix(name, Suffix) { + name += Suffix + } + names = append(names, filepath.Join(path, name)) + } + + for _, filename := range names { + if core.Exists(filename) { + cap := &Caplet{ + Path: filename, + Code: make([]string, 0), + } + + input, err := os.Open(filename) + if err != nil { + return fmt.Errorf("error reading caplet %s: %v", filename, err), nil + } + defer input.Close() + + scanner := bufio.NewScanner(input) + scanner.Split(bufio.ScanLines) + for scanner.Scan() { + line := core.Trim(scanner.Text()) + if line == "" || line[0] == '#' { + continue + } + cap.Code = append(cap.Code, line) + } + + cache[name] = cap + return nil, cap + } + } + + return fmt.Errorf("caplet %s not found", name), nil +} diff --git a/session/caplet.go b/session/caplet.go deleted file mode 100644 index 3711685a..00000000 --- a/session/caplet.go +++ /dev/null @@ -1,153 +0,0 @@ -package session - -import ( - "bufio" - "fmt" - "os" - "path/filepath" - "strings" - "sync" - - "github.com/bettercap/bettercap/core" -) - -const ( - CapletSuffix = ".cap" -) - -type Caplet struct { - Path string - Code []string -} - -var ( - CapletLoadPaths = []string{ - "./caplets/", - "/usr/local/share/bettercap/caplets/", - } - - cache = make(map[string]*Caplet) - cacheLock = sync.Mutex{} -) - -func init() { - for _, path := range core.SepSplit(core.Trim(os.Getenv("CAPSPATH")), ":") { - if path = core.Trim(path); len(path) > 0 { - CapletLoadPaths = append(CapletLoadPaths, path) - } - } - - for i, path := range CapletLoadPaths { - CapletLoadPaths[i], _ = filepath.Abs(path) - } -} - -func LoadCaplet(name string) (error, *Caplet) { - cacheLock.Lock() - defer cacheLock.Unlock() - - if caplet, found := cache[name]; found { - return nil, caplet - } - - names := []string{name} - if !strings.HasSuffix(name, CapletSuffix) { - names = append(names, name+CapletSuffix) - } - - for _, path := range CapletLoadPaths { - if !strings.HasSuffix(name, CapletSuffix) { - name += CapletSuffix - } - names = append(names, filepath.Join(path, name)) - } - - for _, filename := range names { - if core.Exists(filename) { - cap := &Caplet{ - Path: filename, - Code: make([]string, 0), - } - - I.Events.Log(core.INFO, "reading from caplet %s ...", filename) - input, err := os.Open(filename) - if err != nil { - return fmt.Errorf("error reading caplet %s: %v", filename, err), nil - } - defer input.Close() - - scanner := bufio.NewScanner(input) - scanner.Split(bufio.ScanLines) - for scanner.Scan() { - line := core.Trim(scanner.Text()) - if line == "" || line[0] == '#' { - continue - } - cap.Code = append(cap.Code, line) - } - - cache[name] = cap - return nil, cap - } - } - - return fmt.Errorf("caplet %s not found", name), nil -} - -func parseCapletCommand(line string) (is bool, caplet *Caplet, argv []string) { - file := core.Trim(line) - parts := strings.Split(file, " ") - argc := len(parts) - argv = make([]string, 0) - // check for any arguments - if argc > 1 { - file = core.Trim(parts[0]) - if argc >= 2 { - argv = parts[1:] - } - } - - if err, cap := LoadCaplet(file); err == nil { - return true, cap, argv - } - - 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 0207d80d..38a33afa 100644 --- a/session/session.go +++ b/session/session.go @@ -9,10 +9,12 @@ import ( "runtime" "runtime/pprof" "sort" + "strings" "time" "github.com/bettercap/readline" + "github.com/bettercap/bettercap/caplets" "github.com/bettercap/bettercap/core" "github.com/bettercap/bettercap/firewall" "github.com/bettercap/bettercap/network" @@ -255,12 +257,34 @@ func (s *Session) ReadLine() (string, error) { } func (s *Session) RunCaplet(filename string) error { - err, caplet := LoadCaplet(filename) + err, caplet := caplets.Load(filename) if err != nil { return err } - return caplet.Eval(s, nil) + return caplet.Eval(nil, func(line string) error { + return s.Run(line + "\n") + }) +} + +func parseCapletCommand(line string) (is bool, caplet *caplets.Caplet, argv []string) { + file := core.Trim(line) + parts := strings.Split(file, " ") + argc := len(parts) + argv = make([]string, 0) + // check for any arguments + if argc > 1 { + file = core.Trim(parts[0]) + if argc >= 2 { + argv = parts[1:] + } + } + + if err, cap := caplets.Load(file); err == nil { + return true, cap, argv + } + + return false, nil, nil } func (s *Session) Run(line string) error { @@ -294,7 +318,9 @@ func (s *Session) Run(line string) error { // is it a caplet command? if parsed, caplet, argv := parseCapletCommand(line); parsed { - return caplet.Eval(s, argv) + return caplet.Eval(argv, func(line string) error { + return s.Run(line + "\n") + }) } // is it a proxy module custom command?