1package app
2
3import (
4 "context"
5 "encoding/base64"
6 "encoding/json"
7 "errors"
8 "io"
9 "net"
10 "time"
11
12 "github.com/bnema/vev/internal/adapters/webterm"
13)
14
15type webAccess struct {
16 Token string `json:"token"`
17 Settings webterm.Settings `json:"settings"`
18}
19
20func startWebControl(ctx context.Context, server *webterm.Server, settings webterm.Settings) (*net.UnixListener, error) {
21 listener, err := bindWebControl()
22 if err != nil {
23 return nil, err
24 }
25 go func() {
26 stop := context.AfterFunc(ctx, func() { _ = listener.Close() })
27 defer stop()
28 for {
29 conn, err := listener.AcceptUnix()
30 if err != nil {
31 return
32 }
33 // Serialize tiny bounded control requests. No unbounded per-peer workers.
34 func() {
35 defer conn.Close()
36 if !sameWebUID(conn) {
37 return
38 }
39 _ = conn.SetDeadline(time.Now().Add(time.Second))
40 var command [1]byte
41 if _, err := io.ReadFull(conn, command[:]); err != nil {
42 return
43 }
44 var token string
45 switch command[0] {
46 case 'G':
47 token = server.Token()
48 case 'R':
49 token, err = server.RenewToken()
50 if err != nil {
51 return
52 }
53 default:
54 return
55 }
56 _ = json.NewEncoder(conn).Encode(webAccess{Token: token, Settings: settings})
57 }()
58 }
59 }()
60 return listener, nil
61}
62
63func webControlRequest(ctx context.Context, renew bool) (webAccess, error) {
64 ctx, cancel := context.WithTimeout(ctx, time.Second)
65 defer cancel()
66 conn, err := (&net.Dialer{}).DialContext(ctx, "unix", webControlAddress())
67 if err != nil {
68 return webAccess{}, err
69 }
70 defer conn.Close()
71 unixConn, ok := conn.(*net.UnixConn)
72 if !ok {
73 return webAccess{}, errors.New("unsafe web control peer")
74 }
75 if !sameWebUID(unixConn) {
76 return webAccess{}, errors.New("unsafe web control peer")
77 }
78 deadline, _ := ctx.Deadline()
79 _ = conn.SetDeadline(deadline)
80 command := byte('G')
81 if renew {
82 command = 'R'
83 }
84 if _, err := conn.Write([]byte{command}); err != nil {
85 return webAccess{}, err
86 }
87 data, err := io.ReadAll(io.LimitReader(conn, 4097))
88 if err != nil {
89 return webAccess{}, err
90 }
91 var access webAccess
92 if len(data) > 4096 || json.Unmarshal(data, &access) != nil {
93 return webAccess{}, errors.New("invalid web control response")
94 }
95 decoded, err := base64.RawURLEncoding.DecodeString(access.Token)
96 settings, settingsErr := webterm.ParseSettings(access.Settings.Listen, access.Settings.Origin)
97 if err != nil || len(decoded) != 32 || settingsErr != nil || settings != access.Settings {
98 return webAccess{}, errors.New("invalid web control response")
99 }
100 return access, nil
101}