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 operator
4
5import (
6 "bytes"
7 "context"
8 "database/sql"
9 "errors"
10 "golang.org/x/sys/unix"
11 "os"
12 "time"
13)
14
15type CommandExecutor interface {
16 Execute(context.Context, []byte) ([]byte, error)
17}
18type CommandMaterial struct {
19 Role, Origin string
20 Sealer Sealer
21 MAC []byte
22 OtherKeys [][]byte
23 CheckSchema func(context.Context, *sql.Conn) error
24 Build func(*Engine) (CommandExecutor, error)
25}
26
27// RunCommand verifies configuration, private launch transport and signature
28// before exposing the bound database to the role executor.
29func RunCommand(ctx context.Context, database string, material CommandMaterial) (result *CommandResult, err error) {
30 var out []byte
31 if ctx == nil || material.CheckSchema == nil || material.Build == nil || material.Sealer == nil || len(material.MAC) != 32 || !commandOutputPipe() {
32 return nil, ErrRefused
33 }
34 cfg, verifier, err := parseCommandConfig(os.Getenv(commandConfigEnvironment))
35 if err != nil || material.Role != cfg.Role || material.Origin != cfg.Origin {
36 return nil, ErrRefused
37 }
38 for _, key := range material.OtherKeys {
39 if bytes.Equal(key, verifier.key) {
40 return nil, ErrRefused
41 }
42 }
43 invocation, err := openConfiguredInvocation(ctx, database, cfg, verifier)
44 if err != nil {
45 return nil, err
46 }
47 defer func() {
48 if closeErr := invocation.close(); closeErr != nil {
49 err = ErrUnavailable
50 }
51 if err != nil {
52 clear(out)
53 out = nil
54 result = nil
55 }
56 }()
57 engine, err := NewEngine(invocation.database.db, verifier, material.Sealer, material.MAC, time.Now)
58 if err != nil {
59 return nil, err
60 }
61 engine.checkSchema = material.CheckSchema
62 executor, err := material.Build(engine)
63 if err != nil || executor == nil {
64 return nil, ErrRefused
65 }
66 if invocation.inspect {
67 directory, ok := executor.(*Directory)
68 if !ok {
69 return nil, ErrRefused
70 }
71 out, err = directory.Inspect(invocation.ctx, invocation.raw)
72 } else {
73 out, err = executor.Execute(invocation.ctx, invocation.raw)
74 }
75 if err != nil {
76 return nil, err
77 }
78 if len(out) == 0 || len(out) > InputLimit {
79 return nil, ErrUnavailable
80 }
81 deadline, _ := invocation.ctx.Deadline()
82 return &CommandResult{out, deadline}, nil
83}
84
85type CommandResult struct {
86 raw []byte
87 deadline time.Time
88}
89
90func (r *CommandResult) Write(ctx context.Context) error {
91 if r == nil {
92 return ErrRefused
93 }
94 defer func() { clear(r.raw); r.raw = nil }()
95 if ctx == nil || len(r.raw) == 0 || !time.Now().Before(r.deadline) {
96 return ErrUnavailable
97 }
98 if !commandOutputPipe() {
99 return ErrUnavailable
100 }
101 if unix.SetNonblock(1, true) != nil {
102 return ErrUnavailable
103 }
104 written := 0
105 for written < len(r.raw) {
106 if ctx.Err() != nil || !time.Now().Before(r.deadline) {
107 return ErrUnavailable
108 }
109 n, err := unix.Write(1, r.raw[written:])
110 if n > 0 {
111 written += n
112 }
113 if errors.Is(err, unix.EINTR) {
114 continue
115 }
116 if errors.Is(err, unix.EAGAIN) {
117 timer := time.NewTimer(time.Millisecond)
118 select {
119 case <-ctx.Done():
120 timer.Stop()
121 return ErrUnavailable
122 case <-timer.C:
123 }
124 continue
125 }
126 if err != nil || n == 0 {
127 return ErrUnavailable
128 }
129 }
130 if unix.Close(1) != nil || ctx.Err() != nil || !time.Now().Before(r.deadline) {
131 return ErrUnavailable
132 }
133 return nil
134}
135
136func commandOutputPipe() bool {
137 var st unix.Stat_t
138 flags, err := unix.FcntlInt(1, unix.F_GETFL, 0)
139 return err == nil && flags&unix.O_ACCMODE == unix.O_WRONLY && unix.Fstat(1, &st) == nil && st.Mode&unix.S_IFMT == unix.S_IFIFO
140}
141