feat: execute durable client commands through supervisor
This commit is contained in:
@@ -0,0 +1,126 @@
|
||||
// Package windowstray contains the small, versioned protocol between the
|
||||
// per-session notification-area process and the machine-wide service. The
|
||||
// tray never receives command payloads or opens the client spool.
|
||||
package windowstray
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
const (
|
||||
protocolVersion uint16 = 1
|
||||
maxFrameBytes = 64 << 10
|
||||
maxPayloadBytes = 4 << 10
|
||||
)
|
||||
|
||||
var (
|
||||
ErrInvalidFrame = errors.New("invalid tray protocol frame")
|
||||
ErrFrameTooLarge = errors.New("tray protocol frame is too large")
|
||||
ErrUnauthorized = errors.New("tray peer is not authorized for this action")
|
||||
ErrInvalidPeer = errors.New("tray peer identity is not verified")
|
||||
)
|
||||
|
||||
type Action uint16
|
||||
|
||||
const (
|
||||
ActionStatus Action = iota + 1
|
||||
ActionOpenConfig
|
||||
ActionOpenLog
|
||||
ActionStartService
|
||||
ActionStopService
|
||||
ActionRestartService
|
||||
ActionExitTray
|
||||
)
|
||||
|
||||
func (action Action) valid() bool { return action >= ActionStatus && action <= ActionExitTray }
|
||||
|
||||
// Frame is deliberately not an RPC envelope. Payloads are bounded display
|
||||
// text only (status/detail); service mutations use an enum and are rechecked
|
||||
// by the service under the caller's token.
|
||||
type Frame struct {
|
||||
Action Action
|
||||
Payload []byte
|
||||
}
|
||||
|
||||
func Encode(frame Frame) ([]byte, error) {
|
||||
if !frame.Action.valid() || len(frame.Payload) > maxPayloadBytes || !utf8.Valid(frame.Payload) {
|
||||
return nil, ErrInvalidFrame
|
||||
}
|
||||
if frame.Action != ActionStatus && len(frame.Payload) != 0 {
|
||||
return nil, ErrInvalidFrame
|
||||
}
|
||||
total := 4 + 2 + 2 + 4 + len(frame.Payload)
|
||||
if total > maxFrameBytes {
|
||||
return nil, ErrFrameTooLarge
|
||||
}
|
||||
encoded := make([]byte, total)
|
||||
copy(encoded[:4], []byte("RVTY"))
|
||||
binary.BigEndian.PutUint16(encoded[4:6], protocolVersion)
|
||||
binary.BigEndian.PutUint16(encoded[6:8], uint16(frame.Action))
|
||||
binary.BigEndian.PutUint32(encoded[8:12], uint32(len(frame.Payload)))
|
||||
copy(encoded[12:], frame.Payload)
|
||||
return encoded, nil
|
||||
}
|
||||
|
||||
func Decode(encoded []byte) (Frame, error) {
|
||||
if len(encoded) > maxFrameBytes {
|
||||
return Frame{}, ErrFrameTooLarge
|
||||
}
|
||||
if len(encoded) < 12 || !bytes.Equal(encoded[:4], []byte("RVTY")) || binary.BigEndian.Uint16(encoded[4:6]) != protocolVersion {
|
||||
return Frame{}, ErrInvalidFrame
|
||||
}
|
||||
action := Action(binary.BigEndian.Uint16(encoded[6:8]))
|
||||
length := binary.BigEndian.Uint32(encoded[8:12])
|
||||
if !action.valid() || length > maxPayloadBytes || uint64(length)+12 != uint64(len(encoded)) {
|
||||
return Frame{}, ErrInvalidFrame
|
||||
}
|
||||
payload := bytes.Clone(encoded[12:])
|
||||
if !utf8.Valid(payload) || action != ActionStatus && len(payload) != 0 {
|
||||
return Frame{}, ErrInvalidFrame
|
||||
}
|
||||
return Frame{Action: action, Payload: payload}, nil
|
||||
}
|
||||
|
||||
type Peer struct {
|
||||
PID uint32
|
||||
SessionID uint32
|
||||
SID string
|
||||
TokenVerified bool
|
||||
Interactive bool
|
||||
Administrator bool
|
||||
System bool
|
||||
}
|
||||
|
||||
func (peer Peer) Validate() error {
|
||||
if peer.PID == 0 || peer.SessionID == ^uint32(0) || peer.SID == "" || !peer.TokenVerified {
|
||||
return ErrInvalidPeer
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func Authorize(peer Peer, action Action) error {
|
||||
if !action.valid() {
|
||||
return ErrInvalidFrame
|
||||
}
|
||||
if err := peer.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
if !peer.Interactive {
|
||||
return fmt.Errorf("%w: tray peer is not interactive", ErrUnauthorized)
|
||||
}
|
||||
switch action {
|
||||
case ActionStatus, ActionOpenConfig, ActionOpenLog, ActionExitTray:
|
||||
return nil
|
||||
case ActionStartService, ActionStopService, ActionRestartService:
|
||||
if peer.Administrator || peer.System {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("%w: service mutation requires administrator authorization", ErrUnauthorized)
|
||||
default:
|
||||
return ErrInvalidFrame
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user