diff --git a/modules/modules.go b/modules/modules.go index 15e15c4b..fed48f25 100644 --- a/modules/modules.go +++ b/modules/modules.go @@ -24,6 +24,7 @@ import ( "github.com/bettercap/bettercap/modules/syn_scan" "github.com/bettercap/bettercap/modules/tcp_proxy" "github.com/bettercap/bettercap/modules/ticker" + "github.com/bettercap/bettercap/modules/ui" "github.com/bettercap/bettercap/modules/update" "github.com/bettercap/bettercap/modules/wifi" "github.com/bettercap/bettercap/modules/wol" @@ -36,7 +37,6 @@ func LoadModules(sess *session.Session) { sess.Register(arp_spoof.NewArpSpoofer(sess)) sess.Register(api_rest.NewRestAPI(sess)) sess.Register(ble.NewBLERecon(sess)) - sess.Register(caplets.NewCapletsModule(sess)) sess.Register(dhcp6_spoof.NewDHCP6Spoofer(sess)) sess.Register(net_recon.NewDiscovery(sess)) sess.Register(dns_spoof.NewDNSSpoofer(sess)) @@ -54,8 +54,11 @@ func LoadModules(sess *session.Session) { sess.Register(syn_scan.NewSynScanner(sess)) sess.Register(tcp_proxy.NewTcpProxy(sess)) sess.Register(ticker.NewTicker(sess)) - sess.Register(update.NewUpdateModule(sess)) sess.Register(wifi.NewWiFiModule(sess)) sess.Register(wol.NewWOL(sess)) sess.Register(hid.NewHIDRecon(sess)) + + sess.Register(caplets.NewCapletsModule(sess)) + sess.Register(update.NewUpdateModule(sess)) + sess.Register(ui.NewUIModule(sess)) } diff --git a/modules/ui/ui.go b/modules/ui/ui.go new file mode 100644 index 00000000..f5d6950e --- /dev/null +++ b/modules/ui/ui.go @@ -0,0 +1,190 @@ +package ui + +import ( + "context" + "fmt" + "io" + "io/ioutil" + "net/http" + "os" + "path/filepath" + "regexp" + + "github.com/bettercap/bettercap/session" + + "github.com/google/go-github/github" + + "github.com/evilsocket/islazy/fs" + "github.com/evilsocket/islazy/tui" + "github.com/evilsocket/islazy/zip" +) + +var versionParser = regexp.MustCompile(`name:"ui",version:"([^"]+)"`) + +type UIModule struct { + session.SessionModule + client *github.Client + tmpFile string + basePath string + uiPath string +} + +func NewUIModule(s *session.Session) *UIModule { + mod := &UIModule{ + SessionModule: session.NewSessionModule("ui", s), + client: github.NewClient(nil), + } + + mod.AddParam(session.NewStringParameter("ui.basepath", + "/usr/local/share/bettercap/", + "", + "UI base installation path.")) + + mod.AddParam(session.NewStringParameter("ui.tmpfile", + filepath.Join(os.TempDir(), "ui.zip"), + "", + "Temporary file to use while downloading UI updates.")) + + mod.AddHandler(session.NewModuleHandler("ui.version", "", + "Print the currently installed UI version.", + func(args []string) error { + return mod.showVersion() + })) + + mod.AddHandler(session.NewModuleHandler("ui.update", "", + "Download the latest available version of the UI and install it.", + func(args []string) error { + return mod.Start() + })) + + return mod +} + +func (mod *UIModule) Name() string { + return "ui" +} + +func (mod *UIModule) Description() string { + return "A module to manage bettercap's UI updates and installed version." +} + +func (mod *UIModule) Author() string { + return "Simone Margaritelli " +} + +func (mod *UIModule) Configure() (err error) { + if err, mod.basePath = mod.StringParam("ui.basepath"); err != nil { + return err + } else { + mod.uiPath = filepath.Join(mod.basePath, "ui") + } + + if err, mod.tmpFile = mod.StringParam("ui.tmpfile"); err != nil { + return err + } + + return nil +} + +func (mod *UIModule) Stop() error { + return nil +} + +func (mod *UIModule) download(version, url string) error { + if !fs.Exists(mod.basePath) { + mod.Warning("creating ui install path %s ...", mod.basePath) + if err := os.MkdirAll(mod.basePath, os.ModePerm); err != nil { + return err + } + } + + out, err := os.Create(mod.tmpFile) + if err != nil { + return err + } + defer out.Close() + defer os.Remove(mod.tmpFile) + + mod.Info("downloading ui %s from %s ...", tui.Bold(version), url) + + resp, err := http.Get(url) + if err != nil { + return err + } + defer resp.Body.Close() + + if _, err := io.Copy(out, resp.Body); err != nil { + return err + } + + if fs.Exists(mod.uiPath) { + mod.Warning("removing previously installed UI from %s ...", mod.uiPath) + if err := os.RemoveAll(mod.uiPath); err != nil { + return err + } + } + + mod.Info("installing to %s ...", mod.uiPath) + + if _, err = zip.Unzip(mod.tmpFile, mod.basePath); err != nil { + return err + } + + mod.Info("installation complete, you can now run the %s (or https-ui) caplet to start the UI.", tui.Bold("http-ui")) + + return nil +} + +func (mod *UIModule) showVersion() error { + if err := mod.Configure(); err != nil { + return err + } + + if !fs.Exists(mod.uiPath) { + return fmt.Errorf("path %s does not exist, ui not installed", mod.uiPath) + } + + search := filepath.Join(mod.uiPath, "/main.*.js") + matches, err := filepath.Glob(search) + if err != nil { + return err + } else if len(matches) == 0 { + return fmt.Errorf("can't find any main.*.js files in %s", mod.uiPath) + } + + for _, filename := range matches { + if raw, err := ioutil.ReadFile(filename); err != nil { + return err + } else if m := versionParser.FindStringSubmatch(string(raw)); m != nil { + version := m[1] + mod.Info("v%s", version) + return nil + } + } + + return fmt.Errorf("can't parse version from %s", search) +} + +func (mod *UIModule) Start() error { + if err := mod.Configure(); err != nil { + return err + } else if err := mod.SetRunning(true, nil); err != nil { + return err + } + defer mod.SetRunning(false, nil) + + mod.Info("checking latest stable release ...") + + if releases, _, err := mod.client.Repositories.ListReleases(context.Background(), "bettercap", "ui", nil); err == nil { + latest := releases[0] + for _, a := range latest.Assets { + if *a.Name == "ui.zip" { + return mod.download(*latest.TagName, *a.BrowserDownloadURL) + } + } + } else { + mod.Error("error while fetching latest release info from GitHub: %s", err) + } + + return nil +}