diff options
| author | Ophestra Umiker <cat@ophivana.moe> | 2024-11-04 23:08:29 +0900 |
|---|---|---|
| committer | Ophestra Umiker <cat@ophivana.moe> | 2024-11-05 00:07:36 +0900 |
| commit | 714818c8aa1a26450cdfac0828d1a9528fdb95a4 (patch) | |
| tree | 2eb55d76b5cd885d915ca19033cff514dcafd5f4 /main.go | |
| parent | 69cc64ef56c4b46954514d7ef1b93b3861443255 (diff) | |
fortify: implement cleaner argument structure
Signed-off-by: Ophestra Umiker <cat@ophivana.moe>
Diffstat (limited to 'main.go')
| -rw-r--r-- | main.go | 199 |
1 files changed, 187 insertions, 12 deletions
@@ -1,16 +1,26 @@ package main import ( + _ "embed" + "encoding/json" "flag" + "fmt" + "text/tabwriter" + "git.ophivana.moe/security/fortify/dbus" "git.ophivana.moe/security/fortify/internal" "git.ophivana.moe/security/fortify/internal/app" "git.ophivana.moe/security/fortify/internal/fmsg" "git.ophivana.moe/security/fortify/internal/linux" + "git.ophivana.moe/security/fortify/internal/state" + "git.ophivana.moe/security/fortify/internal/system" ) var ( flagVerbose bool + + //go:embed LICENSE + license string ) func init() { @@ -28,29 +38,193 @@ func main() { flag.Parse() fmsg.SetVerbose(flagVerbose) - if os.SdBooted() { - fmsg.VPrintln("system booted with systemd as init system") - } - // root check if os.Geteuid() == 0 { fmsg.Fatal("this program must not run as root") panic("unreachable") } - // version/license/template command early exit - tryVersion() - tryLicense() - tryTemplate() + printHelp := func() { + fmt.Println() + fmt.Println("Usage:\tfortify [-v] COMMAND [OPTIONS]") + fmt.Println() + fmt.Println("Commands:") + w := tabwriter.NewWriter(os.Stdout(), 0, 1, 4, ' ', 0) + commands := [][2]string{ + {"app", "Launch app defined by the specified config file"}, + {"run", "Configure and start a permissive default sandbox"}, + {"ps", "List active apps and their state"}, + {"version", "Show fortify version"}, + {"license", "Show full license text"}, + {"template", "Produce a config template"}, + {"help", "Show this help message"}, + } + for _, c := range commands { + _, _ = fmt.Fprintf(w, "\t%s\t%s\n", c[0], c[1]) + } + if err := w.Flush(); err != nil { + fmsg.Fatalf("cannot print help: %v", err) + } + fmt.Println() + } + + args := flag.Args() + if len(args) == 0 { + printHelp() + fmsg.Exit(0) + } + + switch args[0] { + case "version": // print version string + if v, ok := internal.Check(internal.Version); ok { + fmt.Println(v) + } else { + fmt.Println("impure") + } + fmsg.Exit(0) + case "license": // print embedded license + fmt.Println(license) + fmsg.Exit(0) + case "template": // print full template configuration + if s, err := json.MarshalIndent(app.Template(), "", " "); err != nil { + fmsg.Fatalf("cannot generate template: %v", err) + panic("unreachable") + } else { + fmt.Println(string(s)) + } + fmsg.Exit(0) + case "help": // print help message + printHelp() + fmsg.Exit(0) + case "ps": // print all state info + var w *tabwriter.Writer + state.MustPrintLauncherStateSimpleGlobal(&w, os.Paths().RunDirPath) + if w != nil { + if err := w.Flush(); err != nil { + fmsg.Println("cannot format output:", err) + } + } else { + fmt.Println("No information available") + } + + fmsg.Exit(0) + case "app": // launch app from configuration file + if len(args) < 2 { + fmsg.Fatal("app requires at least 1 argument") + } + + config := new(app.Config) + if f, err := os.Open(args[1]); err != nil { + fmsg.Fatalf("cannot access config file %q: %s", args[1], err) + panic("unreachable") + } else if err = json.NewDecoder(f).Decode(&config); err != nil { + fmsg.Fatalf("cannot parse config file %q: %s", args[1], err) + panic("unreachable") + } + + // append extra args + config.Command = append(config.Command, args[2:]...) + + // invoke app + runApp(config) + case "run": // run app in permissive defaults usage pattern + set := flag.NewFlagSet("run", flag.ExitOnError) + + var ( + dbusConfigSession string + dbusConfigSystem string + dbusID string + mpris bool + dbusVerbose bool + + userName string + enablements [system.ELen]bool + + launchMethodText string + ) + + set.StringVar(&dbusConfigSession, "dbus-config", "builtin", "Path to D-Bus proxy config file, or \"builtin\" for defaults") + set.StringVar(&dbusConfigSystem, "dbus-system", "nil", "Path to system D-Bus proxy config file, or \"nil\" to disable") + set.StringVar(&dbusID, "dbus-id", "", "D-Bus ID of application, leave empty to disable own paths, has no effect if custom config is available") + set.BoolVar(&mpris, "mpris", false, "Allow owning MPRIS D-Bus path, has no effect if custom config is available") + set.BoolVar(&dbusVerbose, "dbus-log", false, "Force logging in the D-Bus proxy") + + set.StringVar(&userName, "u", "chronos", "Passwd name of user to run as") + set.BoolVar(&enablements[system.EWayland], "wayland", false, "Share Wayland socket") + set.BoolVar(&enablements[system.EX11], "X", false, "Share X11 socket and allow connection") + set.BoolVar(&enablements[system.EDBus], "dbus", false, "Proxy D-Bus connection") + set.BoolVar(&enablements[system.EPulse], "pulse", false, "Share PulseAudio socket and cookie") + + methodHelpString := "Method of launching the child process, can be one of \"sudo\"" + if os.SdBooted() { + methodHelpString += ", \"systemd\"" + } + set.StringVar(&launchMethodText, "method", "sudo", methodHelpString) + + // Ignore errors; set is set for ExitOnError. + _ = set.Parse(args[1:]) + + // initialise config from flags + config := &app.Config{ + ID: dbusID, + User: userName, + Command: set.Args(), + Method: launchMethodText, + } + + // enablements from flags + for i := system.Enablement(0); i < system.Enablement(system.ELen); i++ { + if enablements[i] { + config.Confinement.Enablements.Set(i) + } + } + + // parse D-Bus config file from flags if applicable + if enablements[system.EDBus] { + if dbusConfigSession == "builtin" { + config.Confinement.SessionBus = dbus.NewConfig(dbusID, true, mpris) + } else { + if c, err := dbus.NewConfigFromFile(dbusConfigSession); err != nil { + fmsg.Fatalf("cannot load session bus proxy config from %q: %s", dbusConfigSession, err) + } else { + config.Confinement.SessionBus = c + } + } + + // system bus proxy is optional + if dbusConfigSystem != "nil" { + if c, err := dbus.NewConfigFromFile(dbusConfigSystem); err != nil { + fmsg.Fatalf("cannot load system bus proxy config from %q: %s", dbusConfigSystem, err) + } else { + config.Confinement.SystemBus = c + } + } + + // override log from configuration + if dbusVerbose { + config.Confinement.SessionBus.Log = true + config.Confinement.SystemBus.Log = true + } + } + + // invoke app + runApp(config) + default: + fmsg.Fatalf("%q is not a valid command", args[0]) + } + + panic("unreachable") +} - // state query command early exit - tryState() +func runApp(config *app.Config) { + if os.SdBooted() { + fmsg.VPrintln("system booted with systemd as init system") + } - // invoke app a, err := app.New(os) if err != nil { fmsg.Fatalf("cannot create app: %s\n", err) - } else if err = a.Seal(loadConfig()); err != nil { + } else if err = a.Seal(config); err != nil { logBaseError(err, "cannot seal app:") fmsg.Exit(1) } else if err = a.Start(); err != nil { @@ -69,4 +243,5 @@ func main() { fmsg.Println("inner wait failed:", err) } fmsg.Exit(r) + panic("unreachable") } |
