rule_file.bend

bend-osdev-apparmor@0.1.0.0 · 506 lines · 63 definitions

1# bend-osdev-apparmor: file rules (apparmor.d(5), "FILE RULE", "Access
2# Modes"): a pattern, the access modes r w a l k m, and at most one exec
3# transition (ix ux Ux px Px cx Cx pix Pix cix Cix pux PUx cux CUx), or a
4# bare x in a deny rule; leading or trailing permissions; owner; an exec
5# target after ->.
6#
7# Permission bits: r 0, w 1, a 2, l 3, k 4, m 5, x 6. Write implies append
8# (apparmor_parser compiles w to the write and append bits; see the README,
9# "Experiments"), so w sets bit 2 too.
10import Base
11import bend-osdev-apparmor@0.1.0.0/text.bend as X
12import bend-osdev-apparmor@0.1.0.0/glob.bend as G
13import bend-osdev-apparmor@0.1.0.0/perm.bend as P
14import bend-osdev-apparmor@0.1.0.0/token.bend as T
15import bend-osdev-apparmor@0.1.0.0/error.bend as E
16import bend-osdev-apparmor@0.1.0.0/rule.bend as R
17
18type AaXFallback is Data:
19  AaFbNone{}
20  AaFbIx{}
21  AaFbUx{}
22
23# an exec transition: inherit, unconfined, a profile, a child profile; scrub:
24# the upper-case forms, which scrub the environment
25type AaXMode is Data:
26  AaXIx{}
27  AaXUx{scrub: Bool}
28  AaXPx{scrub: Bool, fb: AaXFallback}
29  AaXCx{scrub: Bool, fb: AaXFallback}
30
31# text: the pattern as written (after variable expansion), for aliases and
32# for comparing literal paths
33type AaFileRule is Data:
34  AaFileRule{qual: P.AaQual, text: List<&2, U32>, path: G.AaGlob, perms: P.AaPerms, exec: Maybe<&2, AaXMode>, target: Maybe<&2, List<&2, U32>>}
35
36# a request: the path, whether the task owns the file (fsuid matches), the
37# permissions asked for
38type AaFileQuery is Data:
39  AaFileQuery{path: List<&2, U32>, owner: Bool, req: P.AaPerms}
40
41def AaFile.r() -> Nat:
42  0n
43
44def AaFile.w() -> Nat:
45  1n
46
47def AaFile.a() -> Nat:
48  2n
49
50def AaFile.l() -> Nat:
51  3n
52
53def AaFile.k() -> Nat:
54  4n
55
56def AaFile.m() -> Nat:
57  5n
58
59def AaFile.x() -> Nat:
60  6n
61
62def AaFile.letters() -> List<&2, String>:
63  ["r", "w", "a", "l", "k", "m", "x"]
64
65# Exec modes
66
67def AaXFallback.eq(a: AaXFallback, b: AaXFallback) -> Bool:
68  match a b:
69    case AaFbNone{} AaFbNone{}:
70      True{}
71    case AaFbIx{} AaFbIx{}:
72      True{}
73    case AaFbUx{} AaFbUx{}:
74      True{}
75    case _ _:
76      False{}
77
78def AaXMode.eq(a: AaXMode, b: AaXMode) -> Bool:
79  match a b:
80    case AaXIx{} AaXIx{}:
81      True{}
82    case AaXUx{s} AaXUx{t}:
83      Bool.not(Bool.xor(s, t))
84    case AaXPx{s, f} AaXPx{t, g}:
85      Bool.and(Bool.not(Bool.xor(s, t)), AaXFallback.eq(f, g))
86    case AaXCx{s, f} AaXCx{t, g}:
87      Bool.and(Bool.not(Bool.xor(s, t)), AaXFallback.eq(f, g))
88    case _ _:
89      False{}
90
91def AaXMode.show.fb(f: AaXFallback, up: Bool) -> String:
92  match f:
93    case AaFbNone{}:
94      ""
95    case AaFbIx{}:
96      "i"
97    case AaFbUx{}:
98      Bool.pick(String, up, "U", "u")
99
100def AaXMode.show(m: AaXMode) -> String:
101  match m:
102    case AaXIx{}:
103      "ix"
104    case AaXUx{s}:
105      Bool.pick(String, s, "Ux", "ux")
106    case AaXPx{+s, f}:
107      Bool.pick(String, s, "P", "p") ++ AaXMode.show.fb(f, s) ++ "x"
108    case AaXCx{+s, f}:
109      Bool.pick(String, s, "C", "c") ++ AaXMode.show.fb(f, s) ++ "x"
110
111# Reading a permission string
112
113# the letters read so far of an exec mode
114type AaFpSt is Data:
115  AaFpBase{}
116  AaFpI{}
117  AaFpLp{}
118  AaFpLpi{}
119  AaFpLpu{}
120  AaFpUp{}
121  AaFpUpi{}
122  AaFpUpU{}
123  AaFpLc{}
124  AaFpLci{}
125  AaFpLcu{}
126  AaFpUc{}
127  AaFpUci{}
128  AaFpUcU{}
129  AaFpLu{}
130  AaFpUu{}
131
132type AaFp is Data:
133  AaFp{st: AaFpSt, r: Bool, w: Bool, a: Bool, l: Bool, k: Bool, m: Bool, exec: Maybe<&2, AaXMode>, bare: Bool, ok: Bool}
134
135def AaFp.fail(f: AaFp) -> AaFp:
136  match f:
137    case AaFp{st, r, w, a, l, k, m, e, b, ok}:
138      AaFp{AaFpBase{}, r, w, a, l, k, m, e, b, False{}}
139
140def AaFp.go(f: AaFp, s: AaFpSt) -> AaFp:
141  match f:
142    case AaFp{st, r, w, a, l, k, m, e, b, ok}:
143      AaFp{s, r, w, a, l, k, m, e, b, ok}
144
145def AaFp.exec.go(e: Maybe<&2, AaXMode>, x: AaXMode, r: Bool, w: Bool, a: Bool, l: Bool, k: Bool, m: Bool, b: Bool, ok: Bool) -> AaFp:
146  match e:
147    case None{}:
148      AaFp{AaFpBase{}, r, w, a, l, k, m, Some{x}, b, ok}
149    case Some{y}:
150      AaFp{AaFpBase{}, r, w, a, l, k, m, Some{y}, b, False{}}
151
152def AaFp.exec(f: AaFp, x: AaXMode) -> AaFp:
153  match f:
154    case AaFp{st, r, w, a, l, k, m, e, b, ok}:
155      AaFp.exec.go(e, x, r, w, a, l, k, m, b, ok)
156
157# one of the access letters r w a l k m (n: its bit), or a bare x (n = 6)
158def AaFp.bit(f: AaFp, n: Nat) -> AaFp:
159  match f:
160    case AaFp{st, r, w, a, l, k, m, e, b, ok}:
161      match n:
162        case 0n:
163          AaFp{AaFpBase{}, True{}, w, a, l, k, m, e, b, ok}
164        case 1n+n1:
165          match n1:
166            case 0n:
167              AaFp{AaFpBase{}, r, True{}, a, l, k, m, e, b, ok}
168            case 1n+n2:
169              match n2:
170                case 0n:
171                  AaFp{AaFpBase{}, r, w, True{}, l, k, m, e, b, ok}
172                case 1n+n3:
173                  match n3:
174                    case 0n:
175                      AaFp{AaFpBase{}, r, w, a, True{}, k, m, e, b, ok}
176                    case 1n+n4:
177                      match n4:
178                        case 0n:
179                          AaFp{AaFpBase{}, r, w, a, l, True{}, m, e, b, ok}
180                        case 1n+n5:
181                          match n5:
182                            case 0n:
183                              AaFp{AaFpBase{}, r, w, a, l, k, True{}, e, b, ok}
184                            case 1n+n6:
185                              AaFp{AaFpBase{}, r, w, a, l, k, m, e, True{}, ok}
186
187def AaFp.base(+f: AaFp, +h: U32) -> AaFp:
188  Bool.pick(AaFp, U32.is_eq(h, 114), AaFp.bit(f, 0n), Bool.pick(AaFp, U32.is_eq(h, 119), AaFp.bit(f, 1n), Bool.pick(AaFp, U32.is_eq(h, 97), AaFp.bit(f, 2n), Bool.pick(AaFp, U32.is_eq(h, 108), AaFp.bit(f, 3n), Bool.pick(AaFp, U32.is_eq(h, 107), AaFp.bit(f, 4n), Bool.pick(AaFp, U32.is_eq(h, 109), AaFp.bit(f, 5n), Bool.pick(AaFp, U32.is_eq(h, 120), AaFp.bit(f, 6n), Bool.pick(AaFp, U32.is_eq(h, 105), AaFp.go(f, AaFpI{}), Bool.pick(AaFp, U32.is_eq(h, 112), AaFp.go(f, AaFpLp{}), Bool.pick(AaFp, U32.is_eq(h, 80), AaFp.go(f, AaFpUp{}), Bool.pick(AaFp, U32.is_eq(h, 99), AaFp.go(f, AaFpLc{}), Bool.pick(AaFp, U32.is_eq(h, 67), AaFp.go(f, AaFpUc{}), Bool.pick(AaFp, U32.is_eq(h, 117), AaFp.go(f, AaFpLu{}), Bool.pick(AaFp, U32.is_eq(h, 85), AaFp.go(f, AaFpUu{}), AaFp.fail(f)))))))))))))))
189
190# the letter after a prefix: x ends the mode, i or u/U (when allowed) extend it
191def AaFp.x(+f: AaFp, +h: U32, x: AaXMode) -> AaFp:
192  Bool.pick(AaFp, U32.is_eq(h, 120), AaFp.exec(f, x), AaFp.fail(f))
193
194def AaFp.ext(+f: AaFp, +h: U32, x: AaXMode, +ci: U32, si: AaFpSt, +cu: U32, su: AaFpSt) -> AaFp:
195  Bool.pick(AaFp, U32.is_eq(h, 120), AaFp.exec(f, x), Bool.pick(AaFp, U32.is_eq(h, ci), AaFp.go(f, si), Bool.pick(AaFp, U32.is_eq(h, cu), AaFp.go(f, su), AaFp.fail(f))))
196
197def AaFp.step.st(s: AaFpSt, +f: AaFp, +h: U32) -> AaFp:
198  match s:
199    case AaFpBase{}:
200      AaFp.base(f, h)
201    case AaFpI{}:
202      AaFp.x(f, h, AaXIx{})
203    case AaFpLp{}:
204      AaFp.ext(f, h, AaXPx{False{}, AaFbNone{}}, 105, AaFpLpi{}, 117, AaFpLpu{})
205    case AaFpLpi{}:
206      AaFp.x(f, h, AaXPx{False{}, AaFbIx{}})
207    case AaFpLpu{}:
208      AaFp.x(f, h, AaXPx{False{}, AaFbUx{}})
209    case AaFpUp{}:
210      AaFp.ext(f, h, AaXPx{True{}, AaFbNone{}}, 105, AaFpUpi{}, 85, AaFpUpU{})
211    case AaFpUpi{}:
212      AaFp.x(f, h, AaXPx{True{}, AaFbIx{}})
213    case AaFpUpU{}:
214      AaFp.x(f, h, AaXPx{True{}, AaFbUx{}})
215    case AaFpLc{}:
216      AaFp.ext(f, h, AaXCx{False{}, AaFbNone{}}, 105, AaFpLci{}, 117, AaFpLcu{})
217    case AaFpLci{}:
218      AaFp.x(f, h, AaXCx{False{}, AaFbIx{}})
219    case AaFpLcu{}:
220      AaFp.x(f, h, AaXCx{False{}, AaFbUx{}})
221    case AaFpUc{}:
222      AaFp.ext(f, h, AaXCx{True{}, AaFbNone{}}, 105, AaFpUci{}, 85, AaFpUcU{})
223    case AaFpUci{}:
224      AaFp.x(f, h, AaXCx{True{}, AaFbIx{}})
225    case AaFpUcU{}:
226      AaFp.x(f, h, AaXCx{True{}, AaFbUx{}})
227    case AaFpLu{}:
228      AaFp.x(f, h, AaXUx{False{}})
229    case AaFpUu{}:
230      AaFp.x(f, h, AaXUx{True{}})
231
232def AaFp.step(f: AaFp, +h: U32) -> AaFp:
233  match f:
234    case AaFp{st, r, w, a, l, k, m, e, b, ok}:
235      AaFp.step.st(st, AaFp{AaFpBase{}, r, w, a, l, k, m, e, b, ok}, h)
236
237def AaFp.run(xs: List<&2, U32>, f: AaFp) -> AaFp:
238  match xs:
239    case Nil{}:
240      f
241    case Con{+h, t}:
242      AaFp.run(t, AaFp.step(f, h))
243
244# what a permission string reads as: the bits and the exec mode
245type AaFPerms is Data:
246  AaFPerms{perms: P.AaPerms, exec: Maybe<&2, AaXMode>}
247
248def AaFp.is_some(m: Maybe<&2, AaXMode>) -> Bool:
249  match m:
250    case None{}:
251      False{}
252    case Some{x}:
253      True{}
254
255def AaFp.base_st(s: AaFpSt) -> Bool:
256  match s:
257    case AaFpBase{}:
258      True{}
259    case _:
260      False{}
261
262# valid: whole letters, not w with a, an exec mode only without deny, a bare
263# x only with deny, something granted
264def AaFp.result(f: AaFp, +deny: Bool, src: E.AaSrc) -> Result<&2, &2, E.AaError, AaFPerms>:
265  match f:
266    case AaFp{st, +r, +w, +a, +l, +k, +m, +e, +b, ok}:
267      +ex = AaFp.is_some(e)
268      +good = Bool.and(ok, Bool.and(AaFp.base_st(st), Bool.and(Bool.not(Bool.and(w, a)), Bool.and(Bool.not(Bool.and(deny, ex)), Bool.and(Bool.not(Bool.and(Bool.not(deny), b)), Bool.and(Bool.not(Bool.and(ex, b)), Bool.or(r, Bool.or(w, Bool.or(a, Bool.or(l, Bool.or(k, Bool.or(m, Bool.or(ex, b)))))))))))))
269      Bool.pick(Result<&2, &2, E.AaError, AaFPerms>, good, Done{AaFPerms{P.AaPerms.of_bits([r, w, Bool.or(w, a), l, k, m, Bool.or(ex, b)]), e}}, Fail{E.AaSrc.perms(src)})
270
271# the permission string xs, in a rule that is (or is not) a deny rule
272def AaFile.parse_perms(xs: List<&2, U32>, +deny: Bool, src: E.AaSrc) -> Result<&2, &2, E.AaError, AaFPerms>:
273  AaFp.result(AaFp.run(xs, AaFp{AaFpBase{}, False{}, False{}, False{}, False{}, False{}, False{}, None{}, False{}, True{}}), deny, src)
274
275# a set of file permissions from letters (r w a l k m x), for queries: None
276# if a letter is not one of them
277def AaFile.letter(+h: U32) -> Maybe<&2, Nat>:
278  Bool.pick(Maybe<&2, Nat>, U32.is_eq(h, 114), Some{0n}, Bool.pick(Maybe<&2, Nat>, U32.is_eq(h, 119), Some{1n}, Bool.pick(Maybe<&2, Nat>, U32.is_eq(h, 97), Some{2n}, Bool.pick(Maybe<&2, Nat>, U32.is_eq(h, 108), Some{3n}, Bool.pick(Maybe<&2, Nat>, U32.is_eq(h, 107), Some{4n}, Bool.pick(Maybe<&2, Nat>, U32.is_eq(h, 109), Some{5n}, Bool.pick(Maybe<&2, Nat>, U32.is_eq(h, 120), Some{6n}, None{})))))))
279
280def AaFile.perms.add(m: Maybe<&2, P.AaPerms>, l: Maybe<&2, Nat>) -> Maybe<&2, P.AaPerms>:
281  match m:
282    case None{}:
283      None{}
284    case Some{p}:
285      match l:
286        case None{}:
287          None{}
288        case Some{i}:
289          Some{P.AaPerms.add(p, i)}
290
291def AaFile.perms.go(xs: List<&2, U32>, acc: Maybe<&2, P.AaPerms>) -> Maybe<&2, P.AaPerms>:
292  match xs:
293    case Nil{}:
294      acc
295    case Con{+h, t}:
296      AaFile.perms.go(t, AaFile.perms.add(acc, AaFile.letter(h)))
297
298def AaFile.perms(s: String) -> P.AaPerms:
299  Maybe.default(&2, P.AaPerms, AaFile.perms.go(X.AaText.of_string(s), Some{P.AaPerms.none()}), P.AaPerms.none())
300
301# Reading a rule
302
303# a term that is a pattern: a quoted string, or a word starting with '/' or
304# '{' (after variable expansion)
305def AaFile.is_path(t: T.AaTerm) -> Bool:
306  match t:
307    case T.AaTmWord{+w}:
308      Bool.or(U32.is_eq(X.AaText.head(w), 47), U32.is_eq(X.AaText.head(w), 123))
309    case T.AaTmQuoted{q}:
310      True{}
311    case T.AaTmList{xs}:
312      False{}
313    case T.AaTmCond{k, v}:
314      False{}
315    case T.AaTmArrow{}:
316      False{}
317
318def AaFile.word(t: T.AaTerm, src: E.AaSrc) -> Result<&2, &2, E.AaError, List<&2, U32>>:
319  match t:
320    case T.AaTmWord{w}:
321      Done{w}
322    case T.AaTmQuoted{q}:
323      Fail{E.AaSrc.perms(src)}
324    case T.AaTmList{xs}:
325      Fail{E.AaSrc.syntax(src)}
326    case T.AaTmCond{k, v}:
327      Fail{E.AaSrc.syntax(src)}
328    case T.AaTmArrow{}:
329      Fail{E.AaSrc.syntax(src)}
330
331def AaFile.path_text(t: T.AaTerm, src: E.AaSrc) -> Result<&2, &2, E.AaError, List<&2, U32>>:
332  match t:
333    case T.AaTmWord{w}:
334      Done{w}
335    case T.AaTmQuoted{q}:
336      Done{q}
337    case T.AaTmList{xs}:
338      Fail{E.AaSrc.syntax(src)}
339    case T.AaTmCond{k, v}:
340      Fail{E.AaSrc.syntax(src)}
341    case T.AaTmArrow{}:
342      Fail{E.AaSrc.syntax(src)}
343
344# every alternative of a rule's pattern starts with '/'
345def AaFile.rooted.go(alts: List<&2, List<&2, G.AaGTok>>) -> Bool:
346  match alts:
347    case Nil{}:
348      True{}
349    case Con{a, t}:
350      match a:
351        case Nil{}:
352          False{}
353        case Con{tok, r}:
354          match tok:
355            case G.AaGChar{c}:
356              Bool.and(U32.is_eq(c, 47), AaFile.rooted.go(t))
357            case _:
358              False{}
359
360def AaFile.rooted(g: G.AaGlob) -> Bool:
361  match g:
362    case G.AaGlob{alts, lit}:
363      AaFile.rooted.go(alts)
364
365def AaFile.check_rooted(+g: G.AaGlob, src: E.AaSrc) -> Result<&2, &2, E.AaError, G.AaGlob>:
366  Bool.pick(Result<&2, &2, E.AaError, G.AaGlob>, AaFile.rooted(g), Done{g}, Fail{E.AaSrc.syntax(src)})
367
368# a target needs an exec mode; with the permissions leading, l and a target
369# make a link rule, outside the model (apparmor_parser: "link and exec
370# perms conflict on a file rule using ->")
371def AaFile.target.check(t: Maybe<&2, List<&2, U32>>, +lead: Bool, fp: AaFPerms, src: E.AaSrc) -> Result<&2, &2, E.AaError, AaFPerms>:
372  match t:
373    case None{}:
374      Done{fp}
375    case Some{x}:
376      match fp:
377        case AaFPerms{+p, +e}:
378          Bool.pick(Result<&2, &2, E.AaError, AaFPerms>, Bool.and(AaFp.is_some(e), Bool.not(Bool.and(lead, P.AaPerms.has(p, 3n)))), Done{AaFPerms{p, e}}, Fail{E.AaSrc.perms(src)})
379
380def AaFile.build.go(fp: AaFPerms, q: P.AaQual, ptext: List<&2, U32>, g: G.AaGlob, target: Maybe<&2, List<&2, U32>>) -> Result<&2, &2, E.AaError, AaFileRule>:
381  match fp:
382    case AaFPerms{p, e}:
383      Done{AaFileRule{q, ptext, g, p, e, target}}
384
385def AaFile.build(+q: P.AaQual, +ptext: List<&2, U32>, pw: List<&2, U32>, +target: Maybe<&2, List<&2, U32>>, +lead: Bool, +src: E.AaSrc) -> Result<&2, &2, E.AaError, AaFileRule>:
386  do Result<&2, &2, E.AaError, AaFileRule>:
387    g0 : G.AaGlob <- R.AaRule.glob_text(ptext, src)
388    g : G.AaGlob <- AaFile.check_rooted(g0, src)
389    fp0 : AaFPerms <- AaFile.parse_perms(pw, P.AaQual.deny(q), src)
390    fp : AaFPerms <- AaFile.target.check(target, lead, fp0, src)
391    AaFile.build.go(fp, q, ptext, g, target)
392
393def AaFile.pair.go(q: P.AaQual, pt: T.AaTerm, wt: T.AaTerm, target: Maybe<&2, List<&2, U32>>, lead: Bool, +src: E.AaSrc) -> Result<&2, &2, E.AaError, AaFileRule>:
394  do Result<&2, &2, E.AaError, AaFileRule>:
395    ptext : List<&2, U32> <- AaFile.path_text(pt, src)
396    pw : List<&2, U32> <- AaFile.word(wt, src)
397    AaFile.build(q, ptext, pw, target, lead, src)
398
399# the two terms in either order: pattern and permissions
400def AaFile.pair(+q: P.AaQual, +a: T.AaTerm, +b: T.AaTerm, +target: Maybe<&2, List<&2, U32>>, +src: E.AaSrc) -> Result<&2, &2, E.AaError, AaFileRule>:
401  Bool.pick(Result<&2, &2, E.AaError, AaFileRule>, AaFile.is_path(a), AaFile.pair.go(q, a, b, target, False{}, src), AaFile.pair.go(q, b, a, target, True{}, src))
402
403# `file,` alone: every path, every permission (an allow rule grants
404# r w a l k m and ix; a deny rule denies all of them and x)
405def AaFile.all(q: P.AaQual) -> AaFileRule:
406  match q:
407    case P.AaQual{a, +d, o}:
408      AaFileRule{P.AaQual{a, d, o}, Nil{}, G.AaGlob.all(), P.AaPerms.of_bits([True{}, True{}, True{}, True{}, True{}, True{}, True{}]), Bool.pick(Maybe<&2, AaXMode>, d, None{}, Some{AaXIx{}}), None{}}
409
410def AaFile.target(t: T.AaTerm, src: E.AaSrc) -> Result<&2, &2, E.AaError, List<&2, U32>>:
411  AaFile.path_text(t, src)
412
413def AaFile.parse.arrow(ok: Bool, +q: P.AaQual, +a: T.AaTerm, +b: T.AaTerm, tgt: T.AaTerm, +src: E.AaSrc) -> Result<&2, &2, E.AaError, AaFileRule>:
414  match ok:
415    case False{}:
416      Fail{E.AaSrc.syntax(src)}
417    case True{}:
418      do Result<&2, &2, E.AaError, AaFileRule>:
419        t : List<&2, U32> <- AaFile.target(tgt, src)
420        AaFile.pair(q, a, b, Some{t}, src)
421
422# the terms after the qualifiers (and after `file`, if present)
423def AaFile.parse(ts: List<&2, T.AaTerm>, +q: P.AaQual, +src: E.AaSrc) -> Result<&2, &2, E.AaError, AaFileRule>:
424  match ts:
425    case Nil{}:
426      Done{AaFile.all(q)}
427    case Con{a, r1}:
428      match r1:
429        case Nil{}:
430          Fail{E.AaSrc.syntax(src)}
431        case Con{b, r2}:
432          match r2:
433            case Nil{}:
434              AaFile.pair(q, a, b, None{}, src)
435            case Con{arrow, r3}:
436              match r3:
437                case Nil{}:
438                  Fail{E.AaSrc.syntax(src)}
439                case Con{tgt, r4}:
440                  match r4:
441                    case Nil{}:
442                      AaFile.parse.arrow(T.AaTerm.is_arrow(arrow), q, a, b, tgt, src)
443                    case Con{x, r5}:
444                      Fail{E.AaSrc.syntax(src)}
445
446# Matching
447
448def AaFileRule.qual(r: AaFileRule) -> P.AaQual:
449  match r:
450    case AaFileRule{q, t, g, p, e, tg}:
451      q
452
453def AaFileRule.matches(r: AaFileRule, q: AaFileQuery) -> Bool:
454  match r:
455    case AaFileRule{qu, t, g, p, e, tg}:
456      match q:
457        case AaFileQuery{path, owner, req}:
458          Bool.and(Bool.or(Bool.not(P.AaQual.owner(qu)), owner), G.AaGlob.matches(g, path))
459
460def AaFileRule.matching.go(hit: Bool, r: AaFileRule, rest: List<&2, AaFileRule>) -> List<&2, AaFileRule>:
461  match hit:
462    case True{}:
463      Con{r, rest}
464    case False{}:
465      rest
466
467# the rules that match a request
468def AaFileRule.matching(rs: List<&2, AaFileRule>, +q: AaFileQuery) -> List<&2, AaFileRule>:
469  match rs:
470    case Nil{}:
471      Nil{}
472    case Con{+r, t}:
473      AaFileRule.matching.go(AaFileRule.matches(r, q), r, AaFileRule.matching(t, q))
474
475def AaFileRule.grant(r: AaFileRule) -> P.AaGrant:
476  match r:
477    case AaFileRule{q, t, g, p, e, tg}:
478      P.AaGrant.of(q, p)
479
480def AaFileRule.grants.of(rs: List<&2, AaFileRule>) -> List<&2, P.AaGrant>:
481  match rs:
482    case Nil{}:
483      Nil{}
484    case Con{r, t}:
485      Con{AaFileRule.grant(r), AaFileRule.grants.of(t)}
486
487# what the matching rules grant and deny
488def AaFileRule.grants(rs: List<&2, AaFileRule>, +q: AaFileQuery) -> List<&2, P.AaGrant>:
489  AaFileRule.grants.of(AaFileRule.matching(rs, q))
490
491def AaFileRule.append(xs: List<&2, AaFileRule>, ys: List<&2, AaFileRule>) -> List<&2, AaFileRule>:
492  match xs:
493    case Nil{}:
494      ys
495    case Con{h, t}:
496      Con{h, AaFileRule.append(t, ys)}
497
498def AaFileQuery.req(q: AaFileQuery) -> P.AaPerms:
499  match q:
500    case AaFileQuery{p, o, r}:
501      r
502
503def AaFileQuery.path(q: AaFileQuery) -> List<&2, U32>:
504  match q:
505    case AaFileQuery{p, o, r}:
506      p