oficina / home Public

Clone
git clone https://amadan.net/oficina/home

Plain git — no account needed to clone.

Download

Download this file

1//go:build linux
2
3package provisioner
4
5import (
6 "context"
7 "flag"
8 "io"
9 "os"
10
11 "golang.org/x/term"
12)
13
14func Run(args []string, out io.Writer) error {
15 if len(args) < 2 || args[0] != "provision" || (args[1] != "init" && args[1] != "backup" && args[1] != "restore" && args[1] != "enroll" && args[1] != "prepare") || out == nil {
16 return ErrRefused
17 }
18 flags := flag.NewFlagSet("oficina provision init", flag.ContinueOnError)
19 flags.SetOutput(io.Discard)
20 state := flags.String("state", "", "")
21 floor := flags.String("floor-dir", "", "")
22 output := flags.String("output", "", "")
23 input := flags.String("input", "", "")
24 passwordFD := flags.Int("password-fd", -1, "")
25 planFD := flags.Int("plan-fd", -1, "")
26 requestFD := flags.Int("request-fd", -1, "")
27 if flags.Parse(args[2:]) != nil || flags.NArg() != 0 || *state == "" || *floor == "" {
28 return ErrRefused
29 }
30 if args[1] == "enroll" || args[1] == "prepare" {
31 if *planFD < 3 || *planFD == *passwordFD || *input != "" || *output != "" {
32 return ErrRefused
33 }
34 } else if *planFD != -1 {
35 return ErrRefused
36 }
37 var privateOutput *requestOutput
38 if args[1] == "prepare" {
39 var err error
40 privateOutput, err = privateRequestOutput(*requestFD, *planFD, *passwordFD)
41 if err != nil {
42 return err
43 }
44 defer privateOutput.Close()
45 } else if *requestFD != -1 {
46 return ErrRefused
47 }
48 password, e := privatePassword(*passwordFD)
49 if e != nil {
50 return e
51 }
52 defer clear(password)
53 var info any
54 switch args[1] {
55 case "enroll", "prepare":
56 file := os.NewFile(uintptr(*planFD), "private-plan")
57 if file == nil {
58 return ErrRefused
59 }
60 raw, err := io.ReadAll(io.LimitReader(file, (64<<10)+1))
61 file.Close()
62 if err != nil || len(raw) > 64<<10 {
63 return ErrRefused
64 }
65 if args[1] == "enroll" {
66 info, e = EnrollPlan(*state, *floor, password, raw)
67 } else {
68 info, e = prepareRequest(context.Background(), *state, *floor, password, raw, privateOutput)
69 }
70 clear(raw)
71 case "init":
72 if *output != "" || *input != "" {
73 return ErrRefused
74 }
75 info, e = Initialize(*state, *floor, password)
76 case "backup":
77 if *output == "" || *input != "" {
78 return ErrRefused
79 }
80 info, e = Backup(*state, *floor, *output, password)
81 case "restore":
82 if *input == "" || *output != "" {
83 return ErrRefused
84 }
85 info, e = Restore(*state, *floor, *input, password)
86 }
87 if e != nil {
88 return e
89 }
90 raw, e := canonicalJSON(info)
91 if e != nil {
92 return e
93 }
94 if _, e = out.Write(append(raw, '\n')); e != nil {
95 return ErrUnavailable
96 }
97 return nil
98}
99func privatePassword(fd int) ([]byte, error) {
100 if fd >= 3 {
101 f := os.NewFile(uintptr(fd), "private-password")
102 if f == nil {
103 return nil, ErrRefused
104 }
105 defer f.Close()
106 password, e := io.ReadAll(io.LimitReader(f, 1025))
107 if e != nil || len(password) == 0 || len(password) > 1024 {
108 return nil, ErrRefused
109 }
110 return password, nil
111 }
112 if fd != -1 {
113 return nil, ErrRefused
114 }
115 tty, e := os.OpenFile("/dev/tty", os.O_RDWR, 0)
116 if e != nil {
117 return nil, ErrRefused
118 }
119 defer tty.Close()
120 if !term.IsTerminal(int(tty.Fd())) {
121 return nil, ErrRefused
122 }
123 if _, e = tty.WriteString("Passphrase: "); e != nil {
124 return nil, ErrUnavailable
125 }
126 password, e := term.ReadPassword(int(tty.Fd()))
127 tty.WriteString("\n")
128 if e != nil || len(password) == 0 || len(password) > 1024 {
129 clear(password)
130 return nil, ErrRefused
131 }
132 return password, nil
133}
134