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. 10 import Base 11 import ./text.bend as X 12 import ./glob.bend as G 13 import ./perm.bend as P 14 import ./token.bend as T 15 import ./error.bend as E 16 import ./rule.bend as R 17 18 type 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 25 type 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 33 type 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 38 type AaFileQuery is Data: 39 AaFileQuery{path: List<&2, U32>, owner: Bool, req: P.AaPerms} 40 41 def AaFile.r() -> Nat: 42 0n 43 44 def AaFile.w() -> Nat: 45 1n 46 47 def AaFile.a() -> Nat: 48 2n 49 50 def AaFile.l() -> Nat: 51 3n 52 53 def AaFile.k() -> Nat: 54 4n 55 56 def AaFile.m() -> Nat: 57 5n 58 59 def AaFile.x() -> Nat: 60 6n 61 62 def AaFile.letters() -> List<&2, String>: 63 ["r", "w", "a", "l", "k", "m", "x"] 64 65 # Exec modes 66 67 def 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 78 def 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 91 def 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 100 def 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 114 type 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 132 type 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 135 def 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 140 def 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 145 def 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 152 def 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) 158 def 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 187 def 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 191 def 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 194 def 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 197 def 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 232 def 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 237 def 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 245 type AaFPerms is Data: 246 AaFPerms{perms: P.AaPerms, exec: Maybe<&2, AaXMode>} 247 248 def AaFp.is_some(m: Maybe<&2, AaXMode>) -> Bool: 249 match m: 250 case None{}: 251 False{} 252 case Some{x}: 253 True{} 254 255 def 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 264 def 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 272 def 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 277 def 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 280 def 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 291 def 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 298 def 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) 305 def 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 318 def 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 331 def 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 '/' 345 def 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 360 def AaFile.rooted(g: G.AaGlob) -> Bool: 361 match g: 362 case G.AaGlob{alts, lit}: 363 AaFile.rooted.go(alts) 364 365 def 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 ->") 371 def 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 380 def 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 385 def 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 393 def 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 400 def 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) 405 def 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 410 def AaFile.target(t: T.AaTerm, src: E.AaSrc) -> Result<&2, &2, E.AaError, List<&2, U32>>: 411 AaFile.path_text(t, src) 412 413 def 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) 423 def 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 448 def AaFileRule.qual(r: AaFileRule) -> P.AaQual: 449 match r: 450 case AaFileRule{q, t, g, p, e, tg}: 451 q 452 453 def 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 460 def 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 468 def 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 475 def 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 480 def 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 488 def AaFileRule.grants(rs: List<&2, AaFileRule>, +q: AaFileQuery) -> List<&2, P.AaGrant>: 489 AaFileRule.grants.of(AaFileRule.matching(rs, q)) 490 491 def 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 498 def AaFileQuery.req(q: AaFileQuery) -> P.AaPerms: 499 match q: 500 case AaFileQuery{p, o, r}: 501 r 502 503 def AaFileQuery.path(q: AaFileQuery) -> List<&2, U32>: 504 match q: 505 case AaFileQuery{p, o, r}: 506 p