1package ports
2
3import (
4 "fmt"
5 "strings"
6 "time"
7 "unicode"
8 "unicode/utf8"
9)
10
11const MaxDuration = 24 * time.Hour
12
13// Zero After disables only that action. No deadline implies another action.
14type Config struct {
15 LockAfter, FadeAfter, OffAfter, SleepAfter time.Duration
16 FadeDuration time.Duration
17 Auth AuthConfig
18 // LockOutput names the output (wl_output name, e.g. DP-2) that shows the
19 // lock prompt. Empty, or absent: the output whose lock surface has
20 // keyboard focus, initially the first advertised one. A change applies
21 // from the next lock.
22 LockOutput string
23}
24
25// MaxOutputName bounds LockOutput: neferclient truncates longer wl_output
26// names, which could never match.
27const MaxOutputName = 64
28type AuthMode string
29
30const (
31 AuthModePassword AuthMode = "password"
32 AuthModePIN AuthMode = "pin"
33)
34
35type PINSource string
36
37const (
38 PINSourceEnv PINSource = "env"
39 PINSourcePass PINSource = "pass"
40)
41
42// Only metadata: values resolved by the visual/auth worker never reach policy.
43// PIN mode always falls back to PAM when its selected source is unavailable or
44// invalid. A wrong PIN is authentication failure, never a fallback trigger.
45type AuthConfig struct {
46 Mode AuthMode
47 PINSource PINSource
48 PINEnv, PINEntry string
49}
50
51func Defaults() Config {
52 return Config{LockAfter: 5 * time.Minute, FadeAfter: 6 * time.Minute, FadeDuration: 2 * time.Second, OffAfter: 7 * time.Minute, SleepAfter: 20 * time.Minute, Auth: AuthConfig{Mode: AuthModePassword}}
53}
54func (c Config) Validate() error {
55 for _, d := range []time.Duration{c.LockAfter, c.FadeAfter, c.OffAfter, c.SleepAfter, c.FadeDuration} {
56 if d < 0 || d > MaxDuration {
57 return fmt.Errorf("duration outside 0..24h")
58 }
59 }
60 if len(c.LockOutput) > MaxOutputName || !utf8.ValidString(c.LockOutput) || strings.ContainsFunc(c.LockOutput, unicode.IsControl) {
61 return fmt.Errorf("invalid lock output name")
62 }
63 a := c.Auth
64 for _, r := range []string{string(a.Mode), string(a.PINSource), a.PINEnv, a.PINEntry} {
65 if !utf8.ValidString(r) {
66 return fmt.Errorf("auth metadata is not valid UTF-8")
67 }
68 }
69 if a.Mode != AuthModePassword && a.Mode != AuthModePIN {
70 return fmt.Errorf("unknown auth mode")
71 }
72 for _, r := range []string{a.PINEnv, a.PINEntry} {
73 if len(r) > 4096 {
74 return fmt.Errorf("auth metadata too large")
75 }
76 for _, c := range r {
77 if unicode.IsControl(c) {
78 return fmt.Errorf("control character in auth metadata")
79 }
80 }
81 }
82 if a.PINEnv != "" {
83 for i, r := range a.PINEnv {
84 if !(r == '_' || r >= 'A' && r <= 'Z' || r >= 'a' && r <= 'z' || i > 0 && r >= '0' && r <= '9') {
85 return fmt.Errorf("invalid environment reference")
86 }
87 }
88 }
89 if a.PINEntry != "" {
90 if strings.HasPrefix(a.PINEntry, "-") || strings.HasPrefix(a.PINEntry, "/") || strings.Contains(a.PINEntry, "\\") {
91 return fmt.Errorf("invalid pass entry")
92 }
93 for _, part := range strings.Split(a.PINEntry, "/") {
94 if part == "" || part == "." || part == ".." {
95 return fmt.Errorf("invalid pass entry")
96 }
97 }
98 }
99 switch a.PINSource {
100 case "":
101 if a.Mode == AuthModePIN || a.PINEnv != "" || a.PINEntry != "" {
102 return fmt.Errorf("PIN metadata requires source")
103 }
104 case PINSourceEnv:
105 if a.PINEnv == "" || a.PINEntry != "" {
106 return fmt.Errorf("env source requires only pin-env")
107 }
108 case PINSourcePass:
109 if a.PINEntry == "" || a.PINEnv != "" {
110 return fmt.Errorf("pass source requires only pin-entry")
111 }
112 default:
113 return fmt.Errorf("unknown PIN source")
114 }
115 return nil
116}
117
118type ConfigChanged struct{ Config Config }