//go:build linux package provisioner import ( "context" "flag" "io" "os" "golang.org/x/term" ) func Run(args []string, out io.Writer) error { if len(args) < 2 || args[0] != "provision" || (args[1] != "init" && args[1] != "backup" && args[1] != "restore" && args[1] != "enroll" && args[1] != "prepare") || out == nil { return ErrRefused } flags := flag.NewFlagSet("oficina provision init", flag.ContinueOnError) flags.SetOutput(io.Discard) state := flags.String("state", "", "") floor := flags.String("floor-dir", "", "") output := flags.String("output", "", "") input := flags.String("input", "", "") passwordFD := flags.Int("password-fd", -1, "") planFD := flags.Int("plan-fd", -1, "") requestFD := flags.Int("request-fd", -1, "") if flags.Parse(args[2:]) != nil || flags.NArg() != 0 || *state == "" || *floor == "" { return ErrRefused } if args[1] == "enroll" || args[1] == "prepare" { if *planFD < 3 || *planFD == *passwordFD || *input != "" || *output != "" { return ErrRefused } } else if *planFD != -1 { return ErrRefused } var privateOutput *requestOutput if args[1] == "prepare" { var err error privateOutput, err = privateRequestOutput(*requestFD, *planFD, *passwordFD) if err != nil { return err } defer privateOutput.Close() } else if *requestFD != -1 { return ErrRefused } password, e := privatePassword(*passwordFD) if e != nil { return e } defer clear(password) var info any switch args[1] { case "enroll", "prepare": file := os.NewFile(uintptr(*planFD), "private-plan") if file == nil { return ErrRefused } raw, err := io.ReadAll(io.LimitReader(file, (64<<10)+1)) file.Close() if err != nil || len(raw) > 64<<10 { return ErrRefused } if args[1] == "enroll" { info, e = EnrollPlan(*state, *floor, password, raw) } else { info, e = prepareRequest(context.Background(), *state, *floor, password, raw, privateOutput) } clear(raw) case "init": if *output != "" || *input != "" { return ErrRefused } info, e = Initialize(*state, *floor, password) case "backup": if *output == "" || *input != "" { return ErrRefused } info, e = Backup(*state, *floor, *output, password) case "restore": if *input == "" || *output != "" { return ErrRefused } info, e = Restore(*state, *floor, *input, password) } if e != nil { return e } raw, e := canonicalJSON(info) if e != nil { return e } if _, e = out.Write(append(raw, '\n')); e != nil { return ErrUnavailable } return nil } func privatePassword(fd int) ([]byte, error) { if fd >= 3 { f := os.NewFile(uintptr(fd), "private-password") if f == nil { return nil, ErrRefused } defer f.Close() password, e := io.ReadAll(io.LimitReader(f, 1025)) if e != nil || len(password) == 0 || len(password) > 1024 { return nil, ErrRefused } return password, nil } if fd != -1 { return nil, ErrRefused } tty, e := os.OpenFile("/dev/tty", os.O_RDWR, 0) if e != nil { return nil, ErrRefused } defer tty.Close() if !term.IsTerminal(int(tty.Fd())) { return nil, ErrRefused } if _, e = tty.WriteString("Passphrase: "); e != nil { return nil, ErrUnavailable } password, e := term.ReadPassword(int(tty.Fd())) tty.WriteString("\n") if e != nil || len(password) == 0 || len(password) > 1024 { clear(password) return nil, ErrRefused } return password, nil }