package main import ( "fmt" "os" "os/exec" "path" "reflect" "strconv" "strings" "syscall" "github.com/subgraph/oz" "github.com/codegangsta/cli" ) var PathDpkgDivert string var PathDpkg string var OzConfig *oz.Config var OzProfiles *oz.Profiles var OzProfile *oz.Profile func init() { checkRoot() PathDpkgDivert = checkDpkgDivert() PathDpkg = checkDpkg() } func main() { app := cli.NewApp() app.Name = "oz-utils" app.Usage = "command line interface to install, remove, and create Oz sandboxes\nYou can specify a package name, a binary path, or a Oz profile file " app.Author = "Subgraph" app.Email = "info@subgraph.com" app.Version = oz.OzVersion app.EnableBashCompletion = true flagsHookMode := []cli.Flag{ cli.BoolFlag{ Name: "hook", Usage: "Run in hook mode, not normally used by the end user", }, } flagsForce := []cli.Flag{ cli.BoolFlag{ Name: "force, f", Usage: "Force the command to run through non fatal errors", }, } app.Commands = []cli.Command{ { Name: "config", Usage: "check and show Oz configurations", Subcommands: []cli.Command{ { Name: "check", Usage: "check oz configuration and profiles for errors", Action: handleConfigcheck, }, { Name: "show", Usage: "prints ouf oz configuration", Action: handleConfigshow, }, }, }, { Name: "install", Usage: "install binary diversion for a program", Action: handleInstall, Flags: append(flagsForce, flagsHookMode...), }, { Name: "remove", Usage: "remove a binary diversion for a program", Action: handleRemove, Flags: append(flagsForce, flagsHookMode...), }, { Name: "status", Usage: "show the status of a binary diversion for a program", Action: handleStatus, }, { Name: "create", Usage: "create a new sandbox profile", Action: handleCreate, }, } app.Run(os.Args) } func handleConfigcheck(c *cli.Context) { _, err := oz.LoadConfig(oz.DefaultConfigPath) if err != nil { if !os.IsNotExist(err) { fmt.Fprintf(os.Stderr, "Could not load configuration `%s`: %v\n", oz.DefaultConfigPath, err) os.Exit(1) } } OzConfig = loadConfig() _, err = oz.LoadProfiles(OzConfig.ProfileDir) if err != nil { fmt.Fprintf(os.Stderr, "Unable to load profiles from `%s`: %v\n", OzConfig.ProfileDir, err) os.Exit(1) } fmt.Println("Configurations and profiles ok!") os.Exit(0) } func handleConfigshow(c *cli.Context) { config, err := oz.LoadConfig(oz.DefaultConfigPath) useDefaults := false if err != nil { if os.IsNotExist(err) { config = oz.NewDefaultConfig() useDefaults = true } else { fmt.Fprintf(os.Stderr, "Could not load configuration `%s`: %v\n", oz.DefaultConfigPath, err) os.Exit(1) } } v := reflect.ValueOf(*config) vt := reflect.TypeOf(*config) maxFieldLength := 0 for i := 0; i < v.NumField(); i++ { flen := len(vt.Field(i).Tag.Get("json")) if flen > maxFieldLength { maxFieldLength = flen } } maxValueLength := 0 for i := 0; i < v.NumField(); i++ { sval := fmt.Sprintf("%v", v.Field(i).Interface()) flen := len(sval) if flen > maxValueLength { maxValueLength = flen } } sfmt := "%-" + strconv.Itoa(maxFieldLength) + "s: %-" + strconv.Itoa(maxValueLength) + "v" hfmt := "%-" + strconv.Itoa(maxFieldLength) + "s: %s\n" if !useDefaults { fmt.Printf(hfmt, "Config file", oz.DefaultConfigPath) } else { fmt.Printf(hfmt, "Config file", "Not found - using defaults") } for i := 0; i < len(fmt.Sprintf(sfmt, "", ""))+2; i++ { fmt.Print("#") } fmt.Println("") for i := 0; i < v.NumField(); i++ { fval := fmt.Sprintf("%v", v.Field(i).Interface()) fmt.Printf(sfmt, vt.Field(i).Tag.Get("json"), fval) desc := vt.Field(i).Tag.Get("desc") if desc != "" { fmt.Printf(" # %s", desc) } fmt.Println("") } os.Exit(0) } func handleInstall(c *cli.Context) { OzConfig = loadConfig() pname := c.Args()[0] OzProfile, err := loadProfile(pname, OzConfig.ProfileDir) if err != nil || OzProfile == nil { installExit(c.Bool("hook"), fmt.Errorf("Unable to load profiles for %s (%v).\n", pname, err)) return // For clarity } if OzConfig.DivertSuffix == "" { installExit(c.Bool("hook"), fmt.Errorf("Divert requires a suffix to be set.\n")) return // For clarity } divertInstall := func(cpath string) { isInstalled, err := isDivertInstalled(cpath) if err != nil { fmt.Fprintf(os.Stderr, "Unknown error: %+v\n", err) os.Exit(1) } if isInstalled == true { fmt.Println("Divert already installed for ", cpath) if c.Bool("force") { return } os.Exit(0) } dpkgArgs := []string{ "--add", "--package", "oz", "--rename", "--divert", getBinaryPath(cpath), cpath, } _, err = exec.Command(PathDpkgDivert, dpkgArgs...).Output() if err != nil { fmt.Fprintf(os.Stderr, "Dpkg divert command `%s %+s` failed: %s", PathDpkgDivert, dpkgArgs, err) os.Exit(1) } clientbin := path.Join(OzConfig.PrefixPath, "bin", "oz") err = syscall.Symlink(clientbin, cpath) if err != nil { fmt.Fprintf(os.Stderr, "Failed to create symlink %s", err) os.Exit(1) } fmt.Printf("Successfully installed Oz sandbox for: %s.\n", cpath) } paths := append([]string{}, OzProfile.Path) paths = append(paths, OzProfile.Paths...) for _, pp := range paths { divertInstall(pp) } fmt.Printf("Successfully installed Oz sandbox for: %s.\n", OzProfile.Name) } func handleRemove(c *cli.Context) { OzConfig = loadConfig() pname := c.Args()[0] OzProfile, err := loadProfile(pname, OzConfig.ProfileDir) if err != nil || OzProfile == nil { installExit(c.Bool("hook"), fmt.Errorf("Unable to load profiles for %s.\n", pname)) return // For clarity } if OzConfig.DivertSuffix == "" { installExit(c.Bool("hook"), fmt.Errorf("Divert requires a suffix to be set.\n")) return // For clarity } divertRemove := func(cpath string) { isInstalled, err := isDivertInstalled(cpath) if err != nil { fmt.Fprintf(os.Stderr, "Unknown error: %+v\n", err) os.Exit(1) } if isInstalled == false { fmt.Println("Divert is not installed for ", cpath) if c.Bool("force") { return } os.Exit(0) } os.Remove(cpath) dpkgArgs := []string{ "--rename", "--package", "oz", "--remove", cpath, } _, err = exec.Command(PathDpkgDivert, dpkgArgs...).Output() if err != nil { fmt.Fprintf(os.Stderr, "Dpkg divert command `%s %+s` failed: %s", PathDpkgDivert, dpkgArgs, err) os.Exit(1) } fmt.Printf("Successfully remove jail for: %s.\n", cpath) } paths := append([]string{}, OzProfile.Path) paths = append(paths, OzProfile.Paths...) for _, pp := range paths { divertRemove(pp) } fmt.Printf("Successfully remove jail for: %s.\n", OzProfile.Name) } func handleStatus(c *cli.Context) { OzConfig = loadConfig() pname := c.Args()[0] OzProfile, err := loadProfile(pname, OzConfig.ProfileDir) if err != nil || OzProfile == nil { fmt.Fprintf(os.Stderr, "Unable to load profiles (%s): %v.\n", pname, err) os.Exit(1) } if OzConfig.DivertSuffix == "" { fmt.Fprintf(os.Stderr, "Divert requires a suffix to be set.\n") os.Exit(1) } checkInstalled := func(cpath string) { isInstalled, err := isDivertInstalled(cpath) if err != nil { fmt.Fprintf(os.Stderr, "Unknown error: %+v\n", err) os.Exit(1) } sfmt := "%-37s\033[0m%s\n" if isInstalled { fmt.Printf("\033[0;32m"+sfmt, "Package divert is installed for: ", cpath) } else { fmt.Printf("\033[0;31m"+sfmt, "Package divert is not installed for: ", cpath) } } paths := append([]string{}, OzProfile.Path) paths = append(paths, OzProfile.Paths...) for _, pp := range paths { checkInstalled(pp) } } func handleCreate(c *cli.Context) { OzConfig = loadConfig() fmt.Println("The weasels ran off with this command... please come back later!") os.Exit(1) } /* * UTILITIES */ func checkRoot() { if os.Getuid() != 0 { fmt.Fprintf(os.Stderr, "%s should be used as root.\n", os.Args[0]) os.Exit(1) } } func checkDpkgDivert() string { ddpath, err := exec.LookPath("dpkg-divert") if err != nil { fmt.Fprintln(os.Stderr, "You do not appear to have dpkg-divert, are you not running Debian/Ubuntu?") os.Exit(1) } return ddpath } func checkDpkg() string { dpath, err := exec.LookPath("dpkg") if err != nil { fmt.Fprintln(os.Stderr, "You do not appear to have dpkg, are you not running Debian/Ubuntu?") os.Exit(1) } return dpath } func isDivertInstalled(bpath string) (bool, error) { outp, err := exec.Command(PathDpkgDivert, "--truename", bpath).Output() if err != nil { return false, err } dpath := strings.TrimSpace(string(outp)) isInstalled := (dpath == getBinaryPath(string(bpath))) if isInstalled { _, err := os.Readlink(bpath) if err != nil { return false, fmt.Errorf("`%s` appears to be diverted but is not installed", dpath) } } return isInstalled, nil } func getBinaryPath(bpath string) string { bpath = strings.TrimSpace(string(bpath)) if strings.HasSuffix(bpath, "."+OzConfig.DivertSuffix) == false { bpath += "." + OzConfig.DivertSuffix } return bpath } func loadConfig() *oz.Config { config, err := oz.LoadConfig(oz.DefaultConfigPath) if err != nil { if os.IsNotExist(err) { fmt.Fprintln(os.Stderr, "Configuration file (%s) is missing, using defaults.", oz.DefaultConfigPath) config = oz.NewDefaultConfig() } else { fmt.Fprintln(os.Stderr, "Could not load configuration: %s", oz.DefaultConfigPath, err) os.Exit(1) } } return config } func loadProfile(name, profileDir string) (*oz.Profile, error) { ps, err := oz.LoadProfiles(profileDir) if err != nil { return nil, err } p, err := ps.GetProfileByName(name) if err != nil || p == nil { return ps.GetProfileByPath(name) } return p, nil } func installExit(hook bool, err error) { if hook { os.Exit(0) } else { fmt.Fprintf(os.Stderr, "%s", err) os.Exit(1) } }