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.
14//
15// The kv tags describe the config file keys (decoded by adapters/config). A
16// zero duration is written "off", except fade.duration where 0s is a value.
17// max:"24h" mirrors MaxDuration (kept for the line number; Validate also checks).
18type Config struct {
19 LockAfter time.Duration `kv:"lock.after,off" min:"1ns" max:"24h"`
20 FadeAfter time.Duration `kv:"fade.after,off" min:"1ns" max:"24h"`
21 OffAfter time.Duration `kv:"screens.off-after,off" min:"1ns" max:"24h"`
22 SleepAfter time.Duration `kv:"sleep.after,off" min:"1ns" max:"24h"`
23 FadeDuration time.Duration `kv:"fade.duration" min:"0s" max:"24h"`
24 Auth AuthConfig `kv:"auth"`
25 // LockOutput names the only output (wl_output name, e.g. DP-2) that
26 // shows the lock prompt while it is connected. Empty, or absent: every
27 // output shows it. A change applies from the next lock.
28 LockOutput string `kv:"lock.output,nonempty"`
29}
30
31// MaxOutputName bounds LockOutput: neferclient truncates longer wl_output
32// names, which could never match.
33const MaxOutputName = 64
34
35type AuthMode string
36
37const (
38 AuthModePassword AuthMode = "password"
39 AuthModePIN AuthMode = "pin"
40)
41
42type PINSource string
43
44const (
45 PINSourceEnv PINSource = "env"
46 PINSourcePass PINSource = "pass"
47)
48
49// Only metadata: values resolved by the visual/auth worker never reach policy.
50// PIN mode always falls back to PAM when its selected source is unavailable or
51// invalid. A wrong PIN is authentication failure, never a fallback trigger.
52type AuthConfig struct {
53 Mode AuthMode `kv:"mode" oneof:"password|pin"`
54 PINSource PINSource `kv:"pin-source" oneof:"env|pass"`
55 PINEnv string `kv:"pin-env,nonempty"`
56 PINEntry string `kv:"pin-entry,nonempty"`
57}
58
59func Defaults() Config {
60 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}}
61}
62func (c Config) Validate() error {
63 for _, d := range []time.Duration{c.LockAfter, c.FadeAfter, c.OffAfter, c.SleepAfter, c.FadeDuration} {
64 if d < 0 || d > MaxDuration {
65 return fmt.Errorf("duration outside 0..24h")
66 }
67 }
68 if len(c.LockOutput) > MaxOutputName || !utf8.ValidString(c.LockOutput) || strings.ContainsFunc(c.LockOutput, unicode.IsControl) {
69 return fmt.Errorf("invalid lock output name")
70 }
71 a := c.Auth
72 for _, r := range []string{string(a.Mode), string(a.PINSource), a.PINEnv, a.PINEntry} {
73 if !utf8.ValidString(r) {
74 return fmt.Errorf("auth metadata is not valid UTF-8")
75 }
76 }
77 if a.Mode != AuthModePassword && a.Mode != AuthModePIN {
78 return fmt.Errorf("unknown auth mode")
79 }
80 for _, r := range []string{a.PINEnv, a.PINEntry} {
81 if len(r) > 4096 {
82 return fmt.Errorf("auth metadata too large")
83 }
84 for _, c := range r {
85 if unicode.IsControl(c) {
86 return fmt.Errorf("control character in auth metadata")
87 }
88 }
89 }
90 if a.PINEnv != "" {
91 for i, r := range a.PINEnv {
92 if !(r == '_' || r >= 'A' && r <= 'Z' || r >= 'a' && r <= 'z' || i > 0 && r >= '0' && r <= '9') {
93 return fmt.Errorf("invalid environment reference")
94 }
95 }
96 }
97 if a.PINEntry != "" {
98 if strings.HasPrefix(a.PINEntry, "-") || strings.HasPrefix(a.PINEntry, "/") || strings.Contains(a.PINEntry, "\\") {
99 return fmt.Errorf("invalid pass entry")
100 }
101 for _, part := range strings.Split(a.PINEntry, "/") {
102 if part == "" || part == "." || part == ".." {
103 return fmt.Errorf("invalid pass entry")
104 }
105 }
106 }
107 switch a.PINSource {
108 case "":
109 if a.Mode == AuthModePIN || a.PINEnv != "" || a.PINEntry != "" {
110 return fmt.Errorf("PIN metadata requires source")
111 }
112 case PINSourceEnv:
113 if a.PINEnv == "" || a.PINEntry != "" {
114 return fmt.Errorf("env source requires only pin-env")
115 }
116 case PINSourcePass:
117 if a.PINEntry == "" || a.PINEnv != "" {
118 return fmt.Errorf("pass source requires only pin-entry")
119 }
120 default:
121 return fmt.Errorf("unknown PIN source")
122 }
123 return nil
124}
125
126type ConfigChanged struct{ Config Config }