Skip to main content

yo_resp/dispatch/
acl.rs

1//! Users, the rules that describe what each of them may do, and the gate that
2//! holds every command to them.
3//!
4//! # What an ACL actually is
5//!
6//! A user is a name, a switch saying whether it is usable at all, a list of
7//! password hashes, and one or more selectors. A selector is the interesting
8//! part: a set of commands, a set of key patterns and a set of channel patterns.
9//! A command is allowed if any one selector allows the command and every key and
10//! channel it touches. Selectors are tried in order and the first that says yes
11//! wins, which is how a user can be given two unrelated jobs without either of
12//! them leaking into the other. Without them the only way to say "read anything
13//! under `cache:` and write anything under `job:`" is to allow reads and writes
14//! over both.
15//!
16//! Every user starts with one selector, called the root selector here and in
17//! Redis, which is the one the bare rules land in: `ACL SETUSER u +get ~k:*`
18//! writes into it, and `ACL SETUSER u (+get ~k:*)` adds another one beside it.
19//!
20//! # Why the rules are kept as text as well as bits
21//!
22//! Command permission is a bitmap, one bit a command, because the gate reads it
23//! on every command a guarded server runs and a set of names would mean a lookup
24//! there. But `ACL LIST` and `ACL GETUSER` have to hand back something that can
25//! be fed to `ACL SETUSER` and produce the same user, and a bitmap cannot say
26//! whether `+@read -get` or the eleven names that leaves was what the operator
27//! wrote. Worse, the two are not the same user: the first allows a command added
28//! to the `@read` category later and the second does not.
29//!
30//! So a selector keeps both. The bitmap is what the gate reads and the rule list
31//! is what describes it, and every rule that touches commands appends itself to
32//! the list after removing whatever it overrides. That is Redis's design and it
33//! is the reason `ACL GETUSER` on a user built with categories reads back in
34//! categories. The removal is the fiddly half and it is spelled out at
35//! [`Selector::note`].
36//!
37//! # The one thing here that is not Redis's
38//!
39//! Redis gives every subcommand of a container command its own identity and its
40//! own bit, so `+config|get` sets a bit and `+config|nope` is refused because
41//! there is no such command. There is no subcommand table here yet, which is
42//! D-114, so both go through the mechanism Redis keeps for the other case: a
43//! list of first arguments a command is allowed with. The gate then reaches the
44//! same answer for every rule an operator would actually write, and the
45//! difference is that `+config|nope` is accepted here and refused there. It is
46//! written down as D-137 rather than papered over, and it goes away with the
47//! subcommand table.
48//!
49//! # Where the gate sits
50//!
51//! Right after the password and the transaction refusal and right before the
52//! memory limit, which is where `processCommand` puts it. That ordering is
53//! visible: a command a user is not allowed to run is refused before the server
54//! decides whether it is out of memory, and a command with the wrong number of
55//! arguments is told that rather than told it is not allowed.
56
57use std::collections::VecDeque;
58use std::path::Path;
59use std::sync::Mutex;
60use std::sync::atomic::Ordering::{Acquire, Relaxed, Release};
61use std::sync::atomic::{AtomicBool, AtomicU64};
62
63use yo_common::{Code, Error, Result, glob_matches, sha256};
64
65use super::args::{self, Args, is};
66use super::keyspec::{self, Access};
67use super::table::{self, Spec};
68use super::{Server, Session};
69use crate::reply::Out;
70
71/// The user every connection is on until it says otherwise.
72pub(super) const DEFAULT: &[u8] = b"default";
73
74/// The categories `ACL CAT` lists, in the order it lists them.
75///
76/// The first twenty two are Redis's own list in Redis's own order, which is the
77/// order they are declared in `server.h` and not alphabetical. The nine after
78/// them are the module surfaces this engine has built in. A real server with
79/// those modules loaded reports them here too, so the shape is right even though
80/// a bare server answers twenty two.
81const CATEGORIES: [&str; 31] = [
82    "keyspace",
83    "read",
84    "write",
85    "set",
86    "sortedset",
87    "list",
88    "hash",
89    "string",
90    "array",
91    "bitmap",
92    "hyperloglog",
93    "geo",
94    "stream",
95    "pubsub",
96    "admin",
97    "fast",
98    "slow",
99    "blocking",
100    "dangerous",
101    "connection",
102    "transaction",
103    "scripting",
104    "bloom",
105    "cms",
106    "cuckoo",
107    "graph",
108    "json",
109    "search",
110    "tdigest",
111    "timeseries",
112    "topk",
113];
114
115/// How many words of bitmap it takes to give every command a bit.
116const WORDS: usize = table::count().div_ceil(64);
117
118/// The read bit on a key pattern, which is what `%R~` sets.
119const READ: u8 = 1;
120/// The write bit, which is what `%W~` sets.
121const WRITE: u8 = 2;
122
123/// A key pattern and what it may be used for.
124#[derive(Debug, Clone, PartialEq, Eq)]
125struct Pattern {
126    /// [`READ`], [`WRITE`] or both.
127    flags: u8,
128    /// The glob itself, without the sigil.
129    glob: Vec<u8>,
130}
131
132impl Pattern {
133    /// The pattern written the way `ACL LIST` writes it.
134    ///
135    /// A pattern good for both is written bare, because `%RW~x` and `~x` are the
136    /// same thing and the bare form is the one everybody types. A pattern good
137    /// for one keeps its sigil, because there is no other way to say it.
138    fn describe(&self, into: &mut Vec<u8>) {
139        match self.flags {
140            READ => into.extend_from_slice(b"%R~"),
141            WRITE => into.extend_from_slice(b"%W~"),
142            _ => into.push(b'~'),
143        }
144        into.extend_from_slice(&self.glob);
145    }
146}
147
148/// One set of commands, keys and channels, which is what a permission check runs
149/// against.
150#[derive(Debug, Clone)]
151struct Selector {
152    /// One bit a command, indexed by its row in the table.
153    allowed: [u64; WORDS],
154    /// Whether every command is allowed without looking, which `+@all` sets and
155    /// taking any command away clears.
156    all_commands: bool,
157    /// Whether a command that does not exist yet would be allowed.
158    ///
159    /// Redis keeps this as a reserved bit past the end of the bitmap, set by
160    /// `+@all` and cleared by `-@all` and by nothing else, so that `+@all -get`
161    /// and `-@all +everything-but-get` can be told apart. They describe back
162    /// differently and they behave differently the day a command is added.
163    future: bool,
164    /// Whether every key is allowed.
165    all_keys: bool,
166    /// Whether every channel is allowed.
167    all_channels: bool,
168    /// The command rules in the order they were given, space separated.
169    ///
170    /// Not the bitmap written out. See the module note for why the two are both
171    /// kept.
172    rules: Vec<u8>,
173    /// The first arguments a command is allowed with, when it is not allowed
174    /// outright. Sorted by command index.
175    firstargs: Vec<(u16, Vec<Vec<u8>>)>,
176    /// The first arguments a container command is refused with, when it is
177    /// allowed outright. Sorted by command index.
178    ///
179    /// This is how `+config -config|get` is held. A server with a row a
180    /// subcommand has does it by clearing that row's bit, and the day yo has one
181    /// too this goes away with the rest of D-114.
182    deniedfirst: Vec<(u16, Vec<Vec<u8>>)>,
183    /// The key patterns, in the order they were added.
184    patterns: Vec<Pattern>,
185    /// The channel patterns, in the order they were added.
186    channels: Vec<Vec<u8>>,
187}
188
189impl Selector {
190    /// A selector that allows nothing, which is where every user starts.
191    ///
192    /// Nothing except the channels, which start out either allowed or refused
193    /// depending on `open`, the server's `acl-pubsub-default`. That one setting
194    /// is the whole reason this takes an argument: it is read at the moment a
195    /// selector is made and never again, so a selector made before it changed
196    /// keeps what it was made with.
197    fn new(open: bool) -> Selector {
198        Selector {
199            allowed: [0; WORDS],
200            all_commands: false,
201            future: false,
202            all_keys: false,
203            all_channels: open,
204            rules: Vec::new(),
205            firstargs: Vec::new(),
206            deniedfirst: Vec::new(),
207            patterns: Vec::new(),
208            channels: Vec::new(),
209        }
210    }
211
212    /// Whether the command at `i` is allowed outright.
213    fn bit(&self, i: usize) -> bool {
214        i < table::count() && self.allowed[i / 64] & (1 << (i % 64)) != 0
215    }
216
217    /// Allow or refuse the command at `i`, and forget any first arguments it had.
218    ///
219    /// Taking anything away clears `all_commands`, because that flag is only
220    /// there to let the gate skip the bitmap and it can no longer do that.
221    fn set(&mut self, i: usize, allow: bool) {
222        if i >= table::count() {
223            return;
224        }
225        if allow {
226            self.allowed[i / 64] |= 1 << (i % 64);
227        } else {
228            self.allowed[i / 64] &= !(1 << (i % 64));
229            self.all_commands = false;
230        }
231        self.firstargs.retain(|(at, _)| usize::from(*at) != i);
232        self.deniedfirst.retain(|(at, _)| usize::from(*at) != i);
233    }
234
235    /// Record a command rule, dropping whatever it overrides.
236    ///
237    /// The rule that goes on the end is the one that wins, so anything it makes
238    /// irrelevant has to come off first or the list would grow forever and
239    /// describe the user wrongly. Two things are irrelevant: the same rule
240    /// written before, and, when this rule names a whole command, any earlier
241    /// rule about one of its subcommands. `+get` after `-get|foo` leaves `+get`
242    /// alone, because the subcommand rule cannot survive its parent being
243    /// decided again.
244    ///
245    /// Note that a rule is matched on the name and not on the sign, so `+get`
246    /// removes an earlier `-get` rather than sitting after it.
247    fn note(&mut self, rule: &[u8], allow: bool) {
248        let mut kept: Vec<u8> = Vec::with_capacity(self.rules.len() + rule.len() + 2);
249        for old in self.rules.split(|b| *b == b' ') {
250            if old.is_empty() {
251                continue;
252            }
253            // The sign is not part of the name and is not compared.
254            let name = &old[1..];
255            let same = name == rule;
256            let child =
257                name.len() > rule.len() && name.starts_with(rule) && name[rule.len()] == b'|';
258            if same || child {
259                continue;
260            }
261            if !kept.is_empty() {
262                kept.push(b' ');
263            }
264            kept.extend_from_slice(old);
265        }
266        if !kept.is_empty() {
267            kept.push(b' ');
268        }
269        kept.push(if allow { b'+' } else { b'-' });
270        kept.extend_from_slice(rule);
271        self.rules = kept;
272    }
273
274    /// Allow this command only when its first argument is `first`.
275    fn allow_first(&mut self, i: u16, first: &[u8]) {
276        let lower = first.to_ascii_lowercase();
277        match self.firstargs.binary_search_by_key(&i, |(at, _)| *at) {
278            Ok(at) => {
279                let list = &mut self.firstargs[at].1;
280                if !list.contains(&lower) {
281                    list.push(lower);
282                }
283            }
284            Err(at) => self.firstargs.insert(at, (i, vec![lower])),
285        }
286    }
287
288    /// The first arguments the command at `i` is allowed with.
289    fn firsts(&self, i: u16) -> &[Vec<u8>] {
290        match self.firstargs.binary_search_by_key(&i, |(at, _)| *at) {
291            Ok(at) => &self.firstargs[at].1,
292            Err(_) => &[],
293        }
294    }
295
296    /// Refuse this command when its first argument is `first`.
297    ///
298    /// This clears `all_commands` for the same reason [`Selector::set`] does:
299    /// the flag is only there so the gate can skip the bitmap, and once one
300    /// subcommand is spoken for it can no longer skip anything.
301    fn deny_first(&mut self, i: u16, first: &[u8]) {
302        let lower = first.to_ascii_lowercase();
303        self.all_commands = false;
304        match self.deniedfirst.binary_search_by_key(&i, |(at, _)| *at) {
305            Ok(at) => {
306                let list = &mut self.deniedfirst[at].1;
307                if !list.contains(&lower) {
308                    list.push(lower);
309                }
310            }
311            Err(at) => self.deniedfirst.insert(at, (i, vec![lower])),
312        }
313    }
314
315    /// The first arguments the command at `i` is refused with.
316    fn denied(&self, i: u16) -> &[Vec<u8>] {
317        match self.deniedfirst.binary_search_by_key(&i, |(at, _)| *at) {
318            Ok(at) => &self.deniedfirst[at].1,
319            Err(_) => &[],
320        }
321    }
322
323    /// Everything a rule about commands could have set, back to nothing.
324    fn reset_commands(&mut self, all: bool) {
325        self.allowed = [if all { u64::MAX } else { 0 }; WORDS];
326        self.all_commands = all;
327        self.future = all;
328        self.rules.clear();
329        self.firstargs.clear();
330        self.deniedfirst.clear();
331    }
332
333    /// The command rules written the way `ACL SETUSER` would take them back.
334    ///
335    /// Always starts with `+@all` or `-@all` and then repeats the rule list,
336    /// which is exactly what was fed in after the last time it was cleared. That
337    /// is why this needs no cleverness: the list is already the answer.
338    fn describe_commands(&self) -> Vec<u8> {
339        let mut out = Vec::with_capacity(self.rules.len() + 8);
340        out.extend_from_slice(if self.future { b"+@all" } else { b"-@all" });
341        if !self.rules.is_empty() {
342            out.push(b' ');
343            out.extend_from_slice(&self.rules);
344        }
345        out
346    }
347
348    /// The key patterns, space separated, or `~*`.
349    fn describe_keys(&self) -> Vec<u8> {
350        let mut out = Vec::new();
351        if self.all_keys {
352            out.extend_from_slice(b"~*");
353            return out;
354        }
355        for pattern in &self.patterns {
356            if !out.is_empty() {
357                out.push(b' ');
358            }
359            pattern.describe(&mut out);
360        }
361        out
362    }
363
364    /// The channel patterns, space separated, or `&*`.
365    fn describe_channels(&self) -> Vec<u8> {
366        let mut out = Vec::new();
367        if self.all_channels {
368            out.extend_from_slice(b"&*");
369            return out;
370        }
371        for channel in &self.channels {
372            if !out.is_empty() {
373                out.push(b' ');
374            }
375            out.push(b'&');
376            out.extend_from_slice(channel);
377        }
378        out
379    }
380
381    /// The whole selector as `ACL LIST` writes it inside a user's line.
382    ///
383    /// Keys first, then channels, then commands. The `resetchannels` in front of
384    /// the channel list is not decoration: without it a line read back on a
385    /// server whose `acl-pubsub-default` is `allchannels` would start from every
386    /// channel allowed and the `&x` rules would add nothing.
387    fn describe(&self) -> Vec<u8> {
388        let mut out = self.describe_keys();
389        if !out.is_empty() {
390            out.push(b' ');
391        }
392        if self.all_channels {
393            out.extend_from_slice(b"&* ");
394        } else {
395            out.extend_from_slice(b"resetchannels ");
396            for channel in &self.channels {
397                out.push(b'&');
398                out.extend_from_slice(channel);
399                out.push(b' ');
400            }
401        }
402        out.extend_from_slice(&self.describe_commands());
403        out
404    }
405}
406
407/// What one of these is allowed to say went wrong with a rule.
408///
409/// The sentences are Redis's, word for word, because an operator reading one has
410/// almost certainly found it in Redis's documentation first.
411#[derive(Debug, Clone, Copy, PartialEq, Eq)]
412enum Bad {
413    /// The rule is not one, or is malformed.
414    Syntax,
415    /// A command or category nobody has heard of.
416    Unknown,
417    /// A key pattern after `~*`.
418    AfterAllKeys,
419    /// A channel pattern after `&*`.
420    AfterAllChannels,
421    /// `<password` for a password the user has not got.
422    NoSuchPassword,
423    /// A `#` hash that is not sixty four hexadecimal characters.
424    Hash,
425    /// `+get|set|other`, which Redis has never supported.
426    NestedFirstArg,
427    /// A `(` with no `)`, which is reported differently from all the rest.
428    Unmatched,
429}
430
431impl Bad {
432    /// The sentence after `Error in ACL SETUSER modifier '<rule>': `.
433    fn text(self) -> &'static str {
434        match self {
435            Bad::Syntax | Bad::Unmatched => "Syntax error",
436            Bad::Unknown => "Unknown command or category name in ACL",
437            Bad::AfterAllKeys => {
438                "Adding a pattern after the * pattern (or the 'allkeys' flag) is not valid and does not have any effect. Try 'resetkeys' to start with an empty list of patterns"
439            }
440            Bad::AfterAllChannels => {
441                "Adding a pattern after the * pattern (or the 'allchannels' flag) is not valid and does not have any effect. Try 'resetchannels' to start with an empty list of channels"
442            }
443            Bad::NoSuchPassword => {
444                "The password you are trying to remove from the user does not exist"
445            }
446            Bad::Hash => {
447                "The password hash must be exactly 64 characters and contain only lowercase hexadecimal characters"
448            }
449            Bad::NestedFirstArg => "Allowing first-arg of a subcommand is not supported",
450        }
451    }
452}
453
454/// One account, and everything it is allowed to do.
455#[derive(Debug, Clone)]
456pub(crate) struct User {
457    /// The name, which is also the key it is filed under.
458    name: Vec<u8>,
459    /// Whether it may authenticate at all.
460    enabled: bool,
461    /// Whether any password gets in, which is what `nopass` means.
462    nopass: bool,
463    /// Whether `DEBUG` payload sanitising is skipped for this user.
464    ///
465    /// Nothing reads it here yet, and it is kept and reported because it is on
466    /// every `ACL LIST` line a real server writes and a config file round trip
467    /// that dropped it would quietly change the user.
468    skip_sanitize: bool,
469    /// The SHA-256 hashes of the passwords that get in, lower case hex.
470    passwords: Vec<[u8; 64]>,
471    /// The root selector first and any others after it.
472    selectors: Vec<Selector>,
473}
474
475impl User {
476    /// A new user, off, with no password and allowed nothing.
477    ///
478    /// `open` is the server's `acl-pubsub-default` and goes to the root
479    /// selector, which is the only thing about a new user that a server setting
480    /// has any say in.
481    fn new(name: &[u8], open: bool) -> User {
482        User {
483            name: name.to_vec(),
484            enabled: false,
485            nopass: false,
486            skip_sanitize: false,
487            passwords: Vec::new(),
488            selectors: vec![Selector::new(open)],
489        }
490    }
491
492    /// The user a server with no `aclfile` starts with, which can do anything.
493    fn default_user() -> User {
494        let mut user = User::new(DEFAULT, false);
495        user.enabled = true;
496        user.nopass = true;
497        let root = &mut user.selectors[0];
498        root.reset_commands(true);
499        root.all_keys = true;
500        root.all_channels = true;
501        user
502    }
503
504    /// The flag words `ACL LIST` and `ACL GETUSER` print, in Redis's order.
505    fn flags(&self) -> Vec<&'static str> {
506        let mut out = Vec::with_capacity(3);
507        out.push(if self.enabled { "on" } else { "off" });
508        if self.nopass {
509            out.push("nopass");
510        }
511        // Exactly one of the two is always set, because a user is created with
512        // the sanitising one on and the rules only ever swap them.
513        out.push(if self.skip_sanitize {
514            "skip-sanitize-payload"
515        } else {
516            "sanitize-payload"
517        });
518        out
519    }
520
521    /// The whole user as one `ACL LIST` line, without the leading `user `.
522    fn describe(&self) -> Vec<u8> {
523        let mut out = Vec::with_capacity(64);
524        out.extend_from_slice(&self.name);
525        for flag in self.flags() {
526            out.push(b' ');
527            out.extend_from_slice(flag.as_bytes());
528        }
529        for hash in &self.passwords {
530            out.extend_from_slice(b" #");
531            out.extend_from_slice(hash);
532        }
533        for (at, selector) in self.selectors.iter().enumerate() {
534            out.push(b' ');
535            if at == 0 {
536                out.extend_from_slice(&selector.describe());
537            } else {
538                out.push(b'(');
539                out.extend_from_slice(&selector.describe());
540                out.push(b')');
541            }
542        }
543        out
544    }
545
546    /// Whether nothing this user could be asked to do would be refused.
547    ///
548    /// One selector that allows every command, every key and every channel, and
549    /// no other selectors, because a second one could only ever allow more and
550    /// the first already allows everything. That is the shape the default user
551    /// has on a server nobody has written an ACL for.
552    fn unrestricted(&self) -> bool {
553        self.selectors.len() == 1
554            && self.selectors[0].all_commands
555            && self.selectors[0].all_keys
556            && self.selectors[0].all_channels
557    }
558
559    /// Whether this password gets in.
560    ///
561    /// Every hash is compared whatever the first one said, so the number of
562    /// passwords a user has is not readable from how long a wrong guess took.
563    /// The hashing has already thrown away everything about the guess that a
564    /// timing difference could leak, so this is belt and braces, and it costs
565    /// one compare of thirty two bytes a password.
566    fn admits(&self, password: &[u8]) -> bool {
567        if !self.enabled {
568            return false;
569        }
570        if self.nopass {
571            return true;
572        }
573        let guess = sha256::hex(password);
574        let mut hit = false;
575        for hash in &self.passwords {
576            hit |= same(hash, &guess);
577        }
578        hit
579    }
580}
581
582/// Whether two byte strings are equal, in time that does not depend on where
583/// they stop being equal.
584pub(super) fn same(a: &[u8], b: &[u8]) -> bool {
585    let mut diff = u8::from(a.len() != b.len());
586    for i in 0..a.len().max(b.len()) {
587        diff |= a.get(i).copied().unwrap_or(0) ^ b.get(i).copied().unwrap_or(0xff);
588    }
589    diff == 0
590}
591
592/// Every user on the server, and a counter saying when one last changed.
593///
594/// One lock over the lot, because every path that writes here is an operator
595/// typing a command and every path that reads is either the same or a
596/// connection authenticating. The command gate does not come through here at
597/// all: it holds a clone of the user it is running as, refreshed when the
598/// counter moves, which is what keeps a guarded server's hot path free of this
599/// lock. See [`Session::acl_user`].
600#[derive(Debug)]
601pub(crate) struct Users {
602    /// The users, sorted by name, which is the order `ACL LIST` reports.
603    ///
604    /// Redis keeps them in a radix tree and walks it in order, so the sorting is
605    /// not a nicety, it is the observable order of two commands.
606    table: Mutex<Vec<User>>,
607    /// Bumped whenever anything in the table changes.
608    generation: AtomicU64,
609    /// Whether a connection has to authenticate before it can do anything.
610    guarded: AtomicBool,
611    /// Whether any user here could be refused any command.
612    ///
613    /// The gate reads this and nothing else on a server nobody has written an
614    /// ACL for, which is every server that only ever set `requirepass` and every
615    /// server that did not even do that. Setting a password does not make it
616    /// true: the default user still has every permission, so no command can be
617    /// refused once a connection is past the password.
618    restricted: AtomicBool,
619    /// Whether a selector starts out allowed every channel.
620    ///
621    /// This is `acl-pubsub-default`, and it is here rather than with the other
622    /// settings because the only thing that reads it is the making of a
623    /// selector. It is false, meaning `resetchannels`, on a server nobody has
624    /// told otherwise, which has been the default since Redis 7.
625    open_channels: AtomicBool,
626}
627
628impl Default for Users {
629    fn default() -> Users {
630        Users {
631            table: Mutex::new(vec![User::default_user()]),
632            generation: AtomicU64::new(1),
633            guarded: AtomicBool::new(false),
634            restricted: AtomicBool::new(false),
635            open_channels: AtomicBool::new(false),
636        }
637    }
638}
639
640impl Users {
641    /// Run `f` over the table, and say that it changed if `f` says so.
642    ///
643    /// The two summaries are recomputed here rather than at every call site,
644    /// which is the point of routing every write through one function: there is
645    /// exactly one place that can leave them disagreeing with the table.
646    fn with<T>(&self, f: impl FnOnce(&mut Vec<User>) -> (bool, T)) -> T {
647        let mut held = self.table.lock().unwrap_or_else(|e| e.into_inner());
648        let (changed, out) = f(&mut held);
649        if changed {
650            let guarded = held
651                .binary_search_by(|u| u.name.as_slice().cmp(DEFAULT))
652                .is_ok_and(|at| !held[at].nopass || !held[at].enabled);
653            self.guarded.store(guarded, Relaxed);
654            self.restricted
655                .store(held.iter().any(|u| !u.unrestricted()), Relaxed);
656            // Released after the change and acquired before a reader looks at
657            // its copy, so a session that sees a new number sees the table that
658            // goes with it.
659            self.generation.fetch_add(1, Release);
660        }
661        out
662    }
663
664    /// Whether a selector made from here on starts out allowed every channel.
665    pub(crate) fn open_channels(&self) -> bool {
666        self.open_channels.load(Relaxed)
667    }
668
669    /// Set `acl-pubsub-default`, which changes nothing that already exists.
670    pub(crate) fn set_open_channels(&self, open: bool) {
671        self.open_channels.store(open, Relaxed);
672    }
673
674    /// A copy of the user called `name`, if there is one.
675    fn get(&self, name: &[u8]) -> Option<User> {
676        self.with(|table| {
677            let found = table
678                .binary_search_by(|u| u.name.as_slice().cmp(name))
679                .ok()
680                .map(|at| table[at].clone());
681            (false, found)
682        })
683    }
684
685    /// The number the session's copy of a user is stamped with.
686    fn generation(&self) -> u64 {
687        self.generation.load(Acquire)
688    }
689}
690
691impl Server {
692    /// Whether this server asks connections for a password.
693    ///
694    /// True when the default user has one, which is exactly what `requirepass`
695    /// means: a server whose default user is `nopass` lets an unauthenticated
696    /// connection straight through as that user, and a server whose default user
697    /// has a password does not.
698    #[must_use]
699    pub(crate) fn guarded(&self) -> bool {
700        self.acl.guarded.load(Relaxed)
701    }
702
703    /// Whether the gate has to ask the ACL anything at all.
704    #[must_use]
705    pub(crate) fn restricted(&self) -> bool {
706        self.acl.restricted.load(Relaxed)
707    }
708
709    /// Set or clear the default user's password, where an empty one clears it.
710    ///
711    /// This is the whole of `CONFIG SET requirepass`, and on a real server it is
712    /// the whole of it too: the config option is a way of writing one rule on
713    /// one user. Setting it drops any password the default user already had,
714    /// which is what the reference does and is worth knowing, because it means
715    /// `requirepass` and `ACL SETUSER default >pw` fight rather than add up.
716    pub fn set_password(&self, password: &[u8]) {
717        self.acl.with(|table| {
718            let Ok(at) = table.binary_search_by(|u| u.name.as_slice().cmp(DEFAULT)) else {
719                return (false, ());
720            };
721            let user = &mut table[at];
722            user.passwords.clear();
723            if password.is_empty() {
724                user.nopass = true;
725            } else {
726                user.nopass = false;
727                user.passwords.push(sha256::hex(password));
728            }
729            (true, ())
730        });
731        self.plain.set(password);
732    }
733
734    /// Hand the plain `requirepass` to `f`, which is how `CONFIG GET` writes it.
735    pub(crate) fn with_password<T>(&self, f: impl FnOnce(&[u8]) -> T) -> T {
736        self.plain.with(f)
737    }
738
739    /// The users, for the `ACL` command and for authentication.
740    pub(crate) fn users(&self) -> &Users {
741        &self.acl
742    }
743}
744
745/// The plain `requirepass`, kept beside the hash because `CONFIG GET` reports it.
746///
747/// A real server does the same and it is worth being explicit about why, because
748/// it looks like the hashing was pointless. It is not: the hash is what an ACL
749/// user's password is, and the config file and `ACL LIST` and `ACL GETUSER` all
750/// name it rather than this. This copy exists for one command, `CONFIG GET
751/// requirepass`, which a real server answers with the password itself, and a
752/// server that answered a hash there would break every tool that reads its own
753/// config back.
754#[derive(Debug, Default)]
755pub(crate) struct Plain {
756    /// Empty when there is no `requirepass`.
757    secret: Mutex<Vec<u8>>,
758}
759
760impl Plain {
761    /// Remember the password `CONFIG SET requirepass` was given.
762    fn set(&self, password: &[u8]) {
763        let mut held = self.secret.lock().unwrap_or_else(|e| e.into_inner());
764        held.clear();
765        held.extend_from_slice(password);
766    }
767
768    /// Hand it to `f`, borrowed rather than copied so it lives in one place.
769    fn with<T>(&self, f: impl FnOnce(&[u8]) -> T) -> T {
770        let held = self.secret.lock().unwrap_or_else(|e| e.into_inner());
771        f(&held)
772    }
773}
774
775/// Let this connection in as `user` if the password is right, and say whether
776/// it did.
777///
778/// Answers the same thing for a user that does not exist and a user whose
779/// password was wrong, on purpose: the difference between the two is a list of
780/// user names. Both write the same line to the ACL log, naming the user that
781/// was asked for, which is where an operator goes to find out that somebody has
782/// been guessing.
783///
784/// `args` is the whole command, and the only thing read out of it is the name at
785/// the front, because that is what the log calls the object: a failed `AUTH` and
786/// a failed `HELLO ... AUTH` are otherwise the same row.
787///
788/// The generation is read before the copy is taken rather than after. A write
789/// that lands in between then leaves the session stamped with a number that is
790/// too small, so its next command takes a fresh copy for nothing, and the other
791/// order would leave it stamped with a number that is too large and holding a
792/// user whose rules had already changed.
793pub(super) fn authenticate(
794    server: &Server,
795    session: &mut Session,
796    user: &[u8],
797    password: &[u8],
798    args: Args<'_>,
799    out: &Out,
800) -> bool {
801    let stamp = server.acl.generation();
802    let Some(found) = server.acl.get(user) else {
803        note_auth(server, session, out, args.get(0), user);
804        return false;
805    };
806    if !found.admits(password) {
807        note_auth(server, session, out, args.get(0), user);
808        return false;
809    }
810    session.become_user(stamp, found);
811    session.admit(true);
812    true
813}
814
815// ------------------------------------------------------------------ the rules
816
817/// Apply one rule to `user`, which is `ACLSetUser` written out.
818///
819/// The user level rules are here and everything else falls through to the
820/// selector. That split is Redis's and it is why `ACL SETUSER u (on)` is a
821/// syntax error: `on` is a fact about the account and a selector is a set of
822/// permissions, so there is nowhere in a selector to put it.
823fn set_user(user: &mut User, rule: &[u8], open: bool) -> std::result::Result<(), Bad> {
824    if rule.is_empty() {
825        return Ok(());
826    }
827    if word(rule, b"on") {
828        user.enabled = true;
829    } else if word(rule, b"off") {
830        user.enabled = false;
831    } else if word(rule, b"skip-sanitize-payload") {
832        user.skip_sanitize = true;
833    } else if word(rule, b"sanitize-payload") {
834        user.skip_sanitize = false;
835    } else if word(rule, b"nopass") {
836        user.nopass = true;
837        user.passwords.clear();
838    } else if word(rule, b"resetpass") {
839        user.nopass = false;
840        user.passwords.clear();
841    } else if rule[0] == b'>' || rule[0] == b'#' {
842        let hash = hash_of(rule)?;
843        if !user.passwords.contains(&hash) {
844            user.passwords.push(hash);
845        }
846        // A user with a password is not a `nopass` user, whatever it was.
847        user.nopass = false;
848    } else if rule[0] == b'<' || rule[0] == b'!' {
849        let hash = hash_of(rule)?;
850        let before = user.passwords.len();
851        user.passwords.retain(|held| *held != hash);
852        if user.passwords.len() == before {
853            return Err(Bad::NoSuchPassword);
854        }
855    } else if rule[0] == b'(' && rule[rule.len() - 1] == b')' {
856        let mut selector = Selector::new(open);
857        for word in split(&rule[1..rule.len() - 1]) {
858            set_selector(&mut selector, &word)?;
859        }
860        user.selectors.push(selector);
861    } else if rule[0] == b'(' {
862        return Err(Bad::Unmatched);
863    } else if word(rule, b"clearselectors") {
864        user.selectors.truncate(1);
865    } else if word(rule, b"reset") {
866        let name = std::mem::take(&mut user.name);
867        *user = User::new(&name, open);
868    } else {
869        return set_selector(&mut user.selectors[0], rule);
870    }
871    Ok(())
872}
873
874/// The hash a `>`, `#`, `<` or `!` rule names.
875///
876/// The first form of each pair is a password to hash and the second is a hash
877/// already, and the only difference between them is whether the sixty four
878/// characters are checked or produced.
879fn hash_of(rule: &[u8]) -> std::result::Result<[u8; 64], Bad> {
880    let rest = &rule[1..];
881    if rule[0] == b'>' || rule[0] == b'<' {
882        return Ok(sha256::hex(rest));
883    }
884    let ok = rest.len() == 64
885        && rest
886            .iter()
887            .all(|b| b.is_ascii_digit() || (b'a'..=b'f').contains(b));
888    if !ok {
889        return Err(Bad::Hash);
890    }
891    let mut hash = [0u8; 64];
892    hash.copy_from_slice(rest);
893    Ok(hash)
894}
895
896/// Apply one rule to a selector, which is `ACLSetSelector` written out.
897fn set_selector(selector: &mut Selector, rule: &[u8]) -> std::result::Result<(), Bad> {
898    if word(rule, b"allkeys") || rule == b"~*" {
899        selector.all_keys = true;
900        selector.patterns.clear();
901    } else if word(rule, b"resetkeys") {
902        selector.all_keys = false;
903        selector.patterns.clear();
904    } else if word(rule, b"allchannels") || rule == b"&*" {
905        selector.all_channels = true;
906        selector.channels.clear();
907    } else if word(rule, b"resetchannels") {
908        selector.all_channels = false;
909        selector.channels.clear();
910    } else if word(rule, b"allcommands") || rule == b"+@all" {
911        selector.reset_commands(true);
912    } else if word(rule, b"nocommands") || rule == b"-@all" {
913        selector.reset_commands(false);
914    } else if rule[0] == b'~' || rule[0] == b'%' {
915        add_pattern(selector, rule)?;
916    } else if rule[0] == b'&' {
917        if selector.all_channels {
918            return Err(Bad::AfterAllChannels);
919        }
920        let glob = &rule[1..];
921        if glob.contains(&b' ') {
922            return Err(Bad::Syntax);
923        }
924        if !selector.channels.iter().any(|held| held == glob) {
925            selector.channels.push(glob.to_vec());
926        }
927    } else if rule[0] == b'+' && rule.get(1) != Some(&b'@') {
928        add_command(selector, &rule[1..], true)?;
929    } else if rule[0] == b'-' && rule.get(1) != Some(&b'@') {
930        add_command(selector, &rule[1..], false)?;
931    } else if rule[0] == b'+' || rule[0] == b'-' {
932        let allow = rule[0] == b'+';
933        add_category(selector, &rule[2..], allow)?;
934        // Recorded with the sigil, because `@read` is what has to be written
935        // back and a bare `read` would read as a command name.
936        let mut name = Vec::with_capacity(rule.len() - 1);
937        name.push(b'@');
938        name.extend_from_slice(&rule[2..].to_ascii_lowercase());
939        selector.note(&name, allow);
940    } else {
941        return Err(Bad::Syntax);
942    }
943    Ok(())
944}
945
946/// A `~pattern` or `%RW~pattern` rule.
947fn add_pattern(selector: &mut Selector, rule: &[u8]) -> std::result::Result<(), Bad> {
948    if selector.all_keys {
949        return Err(Bad::AfterAllKeys);
950    }
951    let mut flags = 0u8;
952    let mut at = 1;
953    if rule[0] == b'%' {
954        // The letters run until the `~`, each may appear once, and there has to
955        // be at least one of them. `%~x` and `%RR~x` are both syntax errors. A
956        // rule that ends before the `~` is not: a bare `%R` is a pattern with an
957        // empty glob, which a real server takes and describes back as `%R~`.
958        let mut ok = true;
959        while at < rule.len() {
960            let letter = rule[at].to_ascii_uppercase();
961            if letter == b'R' && flags & READ == 0 {
962                flags |= READ;
963            } else if letter == b'W' && flags & WRITE == 0 {
964                flags |= WRITE;
965            } else if rule[at] == b'~' {
966                at += 1;
967                break;
968            } else {
969                ok = false;
970                break;
971            }
972            at += 1;
973        }
974        if flags == 0 || !ok {
975            return Err(Bad::Syntax);
976        }
977    } else {
978        flags = READ | WRITE;
979    }
980    let glob = &rule[at..];
981    if glob.contains(&b' ') {
982        return Err(Bad::Syntax);
983    }
984    // The same pattern given twice with different letters is one pattern good
985    // for both, which is why `%R~x %W~x` reads back as `~x`.
986    if let Some(held) = selector.patterns.iter_mut().find(|p| p.glob == glob) {
987        held.flags |= flags;
988    } else {
989        selector.patterns.push(Pattern {
990            flags,
991            glob: glob.to_vec(),
992        });
993    }
994    Ok(())
995}
996
997/// A `+command` or `+command|first` rule.
998fn add_command(selector: &mut Selector, name: &[u8], allow: bool) -> std::result::Result<(), Bad> {
999    let Some(bar) = name.iter().rposition(|b| *b == b'|') else {
1000        let Some(spec) = table::lookup(name) else {
1001            return Err(Bad::Unknown);
1002        };
1003        selector.set(table::index_of(spec), allow);
1004        selector.note(&name.to_ascii_lowercase(), allow);
1005        return Ok(());
1006    };
1007    let (head, first) = (&name[..bar], &name[bar + 1..]);
1008    // The command has to exist even though the first argument cannot be checked,
1009    // so `+nosuch|get` is refused and `+get|nosuch` is not.
1010    let Some(spec) = table::lookup(head) else {
1011        return Err(Bad::Unknown);
1012    };
1013    if head.contains(&b'|') {
1014        return Err(Bad::NestedFirstArg);
1015    }
1016    if first.is_empty() {
1017        return Err(Bad::Syntax);
1018    }
1019    let at = u16::try_from(table::index_of(spec)).unwrap_or(u16::MAX);
1020    if allow {
1021        // Nothing to do when the command is already allowed outright, which is
1022        // what makes `+get +get|set` the same user as `+get`.
1023        if !selector.bit(usize::from(at)) {
1024            selector.allow_first(at, first);
1025        }
1026    } else {
1027        // Taking one subcommand away only means anything for a command that has
1028        // subcommands. A real server refuses `-get|nope` because it looks the
1029        // whole name up in the table and GET has no such row, so this refuses it
1030        // too, and for the same answer.
1031        if !super::CONTAINERS.contains(&spec.name) {
1032            return Err(Bad::Unknown);
1033        }
1034        selector.deny_first(at, first);
1035    }
1036    selector.note(&name.to_ascii_lowercase(), allow);
1037    Ok(())
1038}
1039
1040/// A `+@category` or `-@category` rule.
1041fn add_category(selector: &mut Selector, name: &[u8], allow: bool) -> std::result::Result<(), Bad> {
1042    let Some(wanted) = category(name) else {
1043        return Err(Bad::Unknown);
1044    };
1045    for (at, spec) in table::COMMANDS.iter().enumerate() {
1046        if spec.acl.iter().any(|held| &held[1..] == wanted) {
1047            selector.set(at, allow);
1048        }
1049    }
1050    // And then the subcommands that are in the category without their container
1051    // being in it, one first argument at a time. `-@admin` has to reach CONFIG
1052    // GET and leave CONFIG HELP alone, and this is the only thing that knows the
1053    // two are different. See the note on `table::SUBCATS`.
1054    for (container, sub, cats) in table::SUBCATS {
1055        if !cats.iter().any(|held| &held[1..] == wanted) {
1056            continue;
1057        }
1058        let Some(spec) = table::lookup(container.as_bytes()) else {
1059            continue;
1060        };
1061        let at = u16::try_from(table::index_of(spec)).unwrap_or(u16::MAX);
1062        if !allow {
1063            selector.deny_first(at, sub.as_bytes());
1064        } else if !selector.bit(usize::from(at)) {
1065            selector.allow_first(at, sub.as_bytes());
1066        }
1067    }
1068    Ok(())
1069}
1070
1071/// The category called `name`, whatever case it was written in.
1072fn category(name: &[u8]) -> Option<&'static str> {
1073    CATEGORIES
1074        .iter()
1075        .find(|held| name.eq_ignore_ascii_case(held.as_bytes()))
1076        .copied()
1077}
1078
1079/// Whether `rule` is the keyword `word`, case insensitively.
1080fn word(rule: &[u8], keyword: &[u8]) -> bool {
1081    rule.eq_ignore_ascii_case(keyword)
1082}
1083
1084/// Split a selector's inside into rules on runs of spaces.
1085///
1086/// A selector arrives as one argument, `(+get ~k:*)`, so the words inside it
1087/// have to be taken apart here. Redis uses its config file splitter, which
1088/// understands quotes; this does not, because a key pattern with a space in it
1089/// is refused by the rule above anyway and a quoted rule has nowhere to be
1090/// useful.
1091fn split(inside: &[u8]) -> Vec<Vec<u8>> {
1092    inside
1093        .split(|b| *b == b' ')
1094        .filter(|part| !part.is_empty())
1095        .map(<[u8]>::to_vec)
1096        .collect()
1097}
1098
1099// ------------------------------------------------------------- the permissions
1100
1101/// Why a command was refused, ranked the way Redis ranks them.
1102///
1103/// The rank decides which of several selectors' complaints is reported: a user
1104/// with two selectors that both say no is told about the most specific refusal,
1105/// on the grounds that the selector which got as far as looking at a key was the
1106/// one the operator meant to use.
1107#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
1108pub(crate) enum Denied {
1109    /// Not allowed to run the command at all.
1110    Command,
1111    /// Allowed the command, not the key at this argument.
1112    Key(usize),
1113    /// Allowed the command, not the channel at this argument.
1114    Channel(usize),
1115}
1116
1117impl Denied {
1118    /// Redis's numeric ranking, which is what two refusals are compared on.
1119    fn rank(self) -> u8 {
1120        match self {
1121            Denied::Command => 1,
1122            Denied::Key(_) => 2,
1123            Denied::Channel(_) => 4,
1124        }
1125    }
1126
1127    /// The argument the refusal is about, or nought for the command itself.
1128    fn at(self) -> usize {
1129        match self {
1130            Denied::Command => 0,
1131            Denied::Key(at) | Denied::Channel(at) => at,
1132        }
1133    }
1134}
1135
1136/// Where a command's channels are, since they are not in the key specs.
1137///
1138/// Eight commands and no more, which is Redis's own table. The flags in Redis
1139/// are four and only two of them are checked, so what is kept here is the one
1140/// bit that matters, whether the argument is a pattern being subscribed to, plus
1141/// whether the command is checked at all: unsubscribing is always allowed,
1142/// because a client that has lost permission to a channel still has to be able
1143/// to stop listening to it.
1144struct Channels {
1145    /// The first argument that is a channel.
1146    first: usize,
1147    /// How many there are, or `None` for all the rest.
1148    count: Option<usize>,
1149    /// Whether these are patterns, which are matched literally rather than as
1150    /// globs. `PSUBSCRIBE news.*` needs the ACL to hold `&news.*` exactly, not
1151    /// something that matches it, because otherwise `&news.sport` would let a
1152    /// client subscribe to `news.*` and hear everything.
1153    pattern: bool,
1154}
1155
1156/// The channel arguments of `name`, if it has any that are checked.
1157fn channels_of(name: &str) -> Option<Channels> {
1158    let spec = match name {
1159        "subscribe" | "ssubscribe" => Channels {
1160            first: 1,
1161            count: None,
1162            pattern: false,
1163        },
1164        "psubscribe" => Channels {
1165            first: 1,
1166            count: None,
1167            pattern: true,
1168        },
1169        "publish" | "spublish" => Channels {
1170            first: 1,
1171            count: Some(1),
1172            pattern: false,
1173        },
1174        _ => return None,
1175    };
1176    Some(spec)
1177}
1178
1179/// Whether `selector` may reach `key` for `need`.
1180fn key_ok(selector: &Selector, key: &[u8], need: Access) -> bool {
1181    if selector.all_keys {
1182        return true;
1183    }
1184    selector
1185        .patterns
1186        .iter()
1187        .any(|p| Access::from_bits(p.flags).covers(need) && glob_matches(&p.glob, key))
1188}
1189
1190/// Whether `selector` may reach `channel`.
1191fn channel_ok(selector: &Selector, channel: &[u8], pattern: bool) -> bool {
1192    if selector.all_channels {
1193        return true;
1194    }
1195    selector.channels.iter().any(|held| {
1196        if pattern {
1197            held == channel
1198        } else {
1199            glob_matches(held, channel)
1200        }
1201    })
1202}
1203
1204/// Whether one selector allows this whole command, and what it objected to.
1205fn selector_ok(
1206    selector: &Selector,
1207    spec: &'static Spec,
1208    args: Args<'_>,
1209    base: usize,
1210) -> std::result::Result<(), Denied> {
1211    if !selector.all_commands && !spec.flags.contains(&"no_auth") {
1212        let at = table::index_of(spec);
1213        let index = u16::try_from(at).unwrap_or(u16::MAX);
1214        let sub = (args.len() > base + 1).then(|| args.get(base + 1));
1215        let named = |list: &[Vec<u8>]| {
1216            sub.is_some_and(|word| list.iter().any(|held| word.eq_ignore_ascii_case(held)))
1217        };
1218        if selector.bit(at) {
1219            // Allowed outright, unless this is the one subcommand that was taken
1220            // back off it.
1221            if named(selector.denied(index)) {
1222                return Err(Denied::Command);
1223            }
1224        } else if !named(selector.firsts(index)) {
1225            return Err(Denied::Command);
1226        }
1227    }
1228
1229    if !selector.all_keys && keyspec::takes_keys(spec, args, base) {
1230        let mut refused = None;
1231        keyspec::find(spec, args, base, &mut |run| {
1232            if refused.is_some() {
1233                return;
1234            }
1235            let need = run.need();
1236            for i in 0..run.count {
1237                let at = run.first + i * run.step;
1238                if at < args.len() && !key_ok(selector, args.get(at), need) {
1239                    refused = Some(Denied::Key(at));
1240                    return;
1241                }
1242            }
1243        });
1244        if let Some(why) = refused {
1245            return Err(why);
1246        }
1247    }
1248
1249    if !selector.all_channels
1250        && let Some(where_) = channels_of(spec.name)
1251    {
1252        let first = base + where_.first;
1253        let stop = where_
1254            .count
1255            .map_or(args.len(), |n| (first + n).min(args.len()));
1256        for at in first..stop {
1257            if !channel_ok(selector, args.get(at), where_.pattern) {
1258                return Err(Denied::Channel(at));
1259            }
1260        }
1261    }
1262    Ok(())
1263}
1264
1265/// Whether `user` may run this command, and what it objected to.
1266///
1267/// Every selector is tried and the first that says yes wins. When none does, the
1268/// refusal reported is the highest ranked one, and on a tie the one about the
1269/// argument furthest along, which is Redis's rule and is what makes a two
1270/// selector user complain about the key rather than the command.
1271pub(crate) fn permits(
1272    user: &User,
1273    spec: &'static Spec,
1274    args: Args<'_>,
1275    base: usize,
1276) -> std::result::Result<(), Denied> {
1277    // The whole of the cost on a server whose users can do anything, which is
1278    // every server nobody has written an ACL for.
1279    if let Some(root) = user.selectors.first()
1280        && root.all_commands
1281        && root.all_keys
1282        && root.all_channels
1283    {
1284        return Ok(());
1285    }
1286    let mut worst = Denied::Command;
1287    for selector in &user.selectors {
1288        match selector_ok(selector, spec, args, base) {
1289            Ok(()) => return Ok(()),
1290            Err(why) => {
1291                if why.rank() > worst.rank()
1292                    || (why.rank() == worst.rank() && why.at() > worst.at())
1293                {
1294                    worst = why;
1295                }
1296            }
1297        }
1298    }
1299    Err(worst)
1300}
1301
1302/// The sentence a refusal is reported with.
1303///
1304/// `verbose` is the difference between the gate and `ACL DRYRUN`. The gate is
1305/// answering a client that has just been told no and naming the key it asked
1306/// about would tell it which keys exist, so the terse form says only that a key
1307/// was the problem. `ACL DRYRUN` is answering an operator who asked the
1308/// question on purpose and wants to know which key, so it names it.
1309pub(crate) fn refusal(
1310    why: Denied,
1311    user: &[u8],
1312    spec: &'static Spec,
1313    args: Args<'_>,
1314    base: usize,
1315    verbose: bool,
1316) -> String {
1317    let name = String::from_utf8_lossy(user);
1318    match why {
1319        Denied::Command => {
1320            let sub = container_name(spec, args, base);
1321            format!("User {name} has no permissions to run the '{sub}' command")
1322        }
1323        Denied::Key(at) if verbose => {
1324            let key = String::from_utf8_lossy(args.get(at));
1325            format!("User {name} has no permissions to access the '{key}' key")
1326        }
1327        Denied::Key(_) => "No permissions to access a key".to_string(),
1328        Denied::Channel(at) if verbose => {
1329            let channel = String::from_utf8_lossy(args.get(at));
1330            format!("User {name} has no permissions to access the '{channel}' channel")
1331        }
1332        Denied::Channel(_) => "No permissions to access a channel".to_string(),
1333    }
1334}
1335
1336/// The command's name as a refusal spells it, which is `acl|list` and not `acl`.
1337fn container_name(spec: &'static Spec, args: Args<'_>, base: usize) -> String {
1338    if super::CONTAINERS.contains(&spec.name) && args.len() > base + 1 {
1339        let sub = String::from_utf8_lossy(args.get(base + 1)).to_lowercase();
1340        return format!("{}|{sub}", spec.name);
1341    }
1342    spec.name.to_string()
1343}
1344
1345/// Who a connection is, and the copy of that user its commands are checked
1346/// against.
1347///
1348/// Boxed on the session, because it is three allocations and a connection on a
1349/// server with no ACL never reads any of them past the first command.
1350#[derive(Debug)]
1351pub(crate) struct Identity {
1352    /// The name, which is what `ACL WHOAMI` and `CLIENT INFO` report.
1353    name: Vec<u8>,
1354    /// The generation the copy below was taken at.
1355    stamp: u64,
1356    /// The copy itself.
1357    user: User,
1358}
1359
1360impl Default for Identity {
1361    /// A connection starts as the default user, with a copy stamped nought so
1362    /// that the first command it sends fetches the real one.
1363    fn default() -> Identity {
1364        Identity {
1365            name: DEFAULT.to_vec(),
1366            stamp: 0,
1367            user: User::default_user(),
1368        }
1369    }
1370}
1371
1372impl Session {
1373    /// The name this connection is authenticated as.
1374    pub(crate) fn acl_name(&self) -> &[u8] {
1375        &self.acl.name
1376    }
1377
1378    /// Say that this connection has authenticated as `user`.
1379    ///
1380    /// Written to the row as well as here, because `CLIENT LIST` reports the
1381    /// user of every connection and runs on whichever thread the client asking
1382    /// is on, which is very often not this one.
1383    pub(super) fn become_user(&mut self, stamp: u64, user: User) {
1384        yo_alloc::allow(|| {
1385            self.acl.name.clear();
1386            self.acl.name.extend_from_slice(&user.name);
1387        });
1388        self.acl.stamp = stamp;
1389        self.acl.user = user;
1390        self.sock.set_text(|text| &mut text.user, &self.acl.name);
1391    }
1392
1393    /// Put the connection back on the default user, which is what `RESET` does.
1394    pub(super) fn forget_user(&mut self) {
1395        *self.acl = Identity::default();
1396        // Empty rather than the name, which is how the row spells the default
1397        // user, so a connection that never authenticated costs nothing.
1398        self.sock.set_text(|text| &mut text.user, b"");
1399    }
1400
1401    /// The generation the copy was taken at.
1402    fn acl_stamp(&self) -> u64 {
1403        self.acl.stamp
1404    }
1405
1406    /// Take a fresh copy, or keep the one there is if the user has gone.
1407    fn acl_refresh(&mut self, now: u64, fresh: Option<User>) {
1408        self.acl.stamp = now;
1409        if let Some(user) = fresh {
1410            self.acl.user = user;
1411        }
1412    }
1413
1414    /// The copy itself.
1415    fn acl_cached(&self) -> &User {
1416        &self.acl.user
1417    }
1418}
1419
1420/// What the connection is running as, refreshed if the table has moved on.
1421///
1422/// A session keeps a copy of its user rather than a handle into the table,
1423/// because the alternative is taking the table's lock on every command on every
1424/// connection. The copy is stamped with the generation it was taken at and
1425/// replaced when that number moves, so a `SETUSER` that tightens a user's
1426/// permissions reaches every connection already authenticated as it, on that
1427/// connection's next command. That is what a real server does, and it is the
1428/// half of `SETUSER` that would be easy to get wrong: the point of tightening a
1429/// user is usually the connection that is already open.
1430///
1431/// A user deleted out from under a connection leaves the copy in place, which is
1432/// also Redis's behaviour: `ACL DELUSER` closes those connections rather than
1433/// leaving them running as a user that no longer exists.
1434fn current<'a>(server: &Server, session: &'a mut Session) -> &'a User {
1435    let now = server.acl.generation();
1436    if session.acl_stamp() != now {
1437        let fresh = server.acl.get(session.acl_name());
1438        session.acl_refresh(now, fresh);
1439    }
1440    session.acl_cached()
1441}
1442
1443/// The gate, which is every command on a server that has an ACL worth checking.
1444///
1445/// `None` means the command may run. The refusal is written by the caller
1446/// rather than here, because it is one of the errors that kills an open
1447/// transaction and the caller is what knows about transactions.
1448pub(super) fn gate(
1449    server: &Server,
1450    session: &mut Session,
1451    spec: &'static Spec,
1452    args: Args<'_>,
1453    out: &Out,
1454) -> Option<String> {
1455    // The user is borrowed out of the session and the log wants the session
1456    // back, so everything read off the user is copied out here and the borrow
1457    // ends with the block. It costs one name and one sentence on a command that
1458    // has just been refused, which is not a path anything is timed on.
1459    let (why, said) = {
1460        let user = current(server, session);
1461        let why = permits(user, spec, args, 0).err()?;
1462        // The code is in the line rather than in front of it, because the line
1463        // goes two places: straight into the reply, and spliced into the
1464        // `EXECABORT` an `EXEC` gets. The same reason `NOAUTH` is a whole line.
1465        (why, refusal(why, &user.name, spec, args, 0, false))
1466    };
1467    yo_alloc::allow(|| {
1468        let object = match why {
1469            // The name a refusal spells, which is `acl|list` and not `acl`, and
1470            // is the same string the sentence above names.
1471            Denied::Command => container_name(spec, args, 0).into_bytes(),
1472            Denied::Key(at) | Denied::Channel(at) => args.get(at).to_vec(),
1473        };
1474        let name = session.acl_name().to_vec();
1475        server
1476            .acl_log()
1477            .note(server, session, out, why.reason(), object, name);
1478        Some(format!("NOPERM {said}"))
1479    })
1480}
1481
1482// ----------------------------------------------------------------- the log
1483
1484/// How long two refusals can be apart and still be counted as the same one.
1485///
1486/// A minute, which is Redis's `ACL_LOG_GROUPING_MAX_TIME_DELTA`. The point of it
1487/// is that a client stuck in a retry loop against a command it may not run fills
1488/// the log with one entry and a count rather than with a hundred and twenty eight
1489/// copies of itself, and the operator who comes to look still sees what else
1490/// happened.
1491const GROUPING_MS: u64 = 60_000;
1492
1493/// How far back a new refusal looks for one it matches.
1494///
1495/// Ten, which is Redis's `toscan`. It is a bound on the work rather than on the
1496/// grouping: a refusal that would have matched the eleventh entry gets its own
1497/// row instead, and the operator sees two rows where a longer scan would have
1498/// shown one.
1499const SCAN: usize = 10;
1500
1501/// Why something was refused, which is what `ACL LOG` reports as `reason`.
1502///
1503/// Redis has a fifth, `tls-cert`, for a client certificate that named a user the
1504/// server has not got. There is no TLS here, so there is no way to reach it and
1505/// no value for it, and the counter it feeds in `INFO stats` is reported as the
1506/// nought it would always be.
1507#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1508pub(crate) enum Reason {
1509    /// A command the user may not run.
1510    Command,
1511    /// A key the user may not reach.
1512    Key,
1513    /// A channel the user may not reach.
1514    Channel,
1515    /// A password that did not get in.
1516    Auth,
1517}
1518
1519impl Reason {
1520    /// The word `ACL LOG` prints.
1521    fn name(self) -> &'static str {
1522        match self {
1523            Reason::Command => "command",
1524            Reason::Key => "key",
1525            Reason::Channel => "channel",
1526            Reason::Auth => "auth",
1527        }
1528    }
1529}
1530
1531impl Denied {
1532    /// The reason a refused command is logged under.
1533    fn reason(self) -> Reason {
1534        match self {
1535            Denied::Command => Reason::Command,
1536            Denied::Key(_) => Reason::Key,
1537            Denied::Channel(_) => Reason::Channel,
1538        }
1539    }
1540}
1541
1542/// Where the refused command came from, which is what `ACL LOG` reports as
1543/// `context`.
1544///
1545/// Redis has a fourth, `module`, for a command a module ran on a user's behalf.
1546/// Nothing here runs a command from a module, so there is no way to reach it.
1547#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1548pub(crate) enum Context {
1549    /// The client sent it.
1550    Toplevel,
1551    /// The client was queueing it into a transaction.
1552    Multi,
1553    /// A script called it.
1554    Lua,
1555}
1556
1557impl Context {
1558    /// The word `ACL LOG` prints.
1559    fn name(self) -> &'static str {
1560        match self {
1561            Context::Toplevel => "toplevel",
1562            Context::Multi => "multi",
1563            Context::Lua => "lua",
1564        }
1565    }
1566
1567    /// Where this connection's next refusal is coming from.
1568    ///
1569    /// A command being queued is `multi` and a command `EXEC` is replaying is
1570    /// not, which is Redis's answer and falls out of where the gate sits: the
1571    /// check happens as the command is queued, and the replay does not go
1572    /// through it again.
1573    fn of(session: &Session) -> Context {
1574        if session.scripted {
1575            Context::Lua
1576        } else if session.in_multi() {
1577            Context::Multi
1578        } else {
1579            Context::Toplevel
1580        }
1581    }
1582}
1583
1584/// One refusal, which is one row of `ACL LOG`.
1585#[derive(Debug)]
1586struct Entry {
1587    /// How many refusals have been folded into this row.
1588    count: u64,
1589    reason: Reason,
1590    context: Context,
1591    /// What was refused: the command's name, or the key or channel it named, or
1592    /// the command the password was sent with.
1593    object: Vec<u8>,
1594    /// Who was refused, which for a failed `AUTH` is who they said they were.
1595    username: Vec<u8>,
1596    /// When the last refusal folded into this row happened.
1597    ctime: u64,
1598    /// The `CLIENT INFO` line of the connection that was refused.
1599    cinfo: String,
1600    /// Which refusal this was, counted over the life of the server.
1601    entry_id: u64,
1602    /// When the first refusal folded into this row happened.
1603    created: u64,
1604}
1605
1606/// The refusals, newest first, and the counters that go with them.
1607///
1608/// Its own lock rather than a corner of [`Users`], because the two are touched
1609/// at opposite moments: the table is read on the command path and written by an
1610/// operator, and this is written only when something has already gone wrong. A
1611/// server whose users can do what they are asking for never takes this lock at
1612/// all.
1613#[derive(Debug)]
1614pub(crate) struct Log {
1615    /// Newest at the front, which is the order `ACL LOG` reports.
1616    entries: Mutex<VecDeque<Entry>>,
1617    /// How many rows have ever been opened, which is the next `entry-id`.
1618    next_id: AtomicU64,
1619    /// `acllog-max-len`, and nought means keep nothing.
1620    max_len: AtomicU64,
1621    /// The four counters `INFO stats` reports, in the order [`Reason`] declares
1622    /// them.
1623    denied: [AtomicU64; 4],
1624}
1625
1626impl Default for Log {
1627    fn default() -> Log {
1628        Log {
1629            entries: Mutex::new(VecDeque::new()),
1630            next_id: AtomicU64::new(0),
1631            // A hundred and twenty eight, which is the reference's default.
1632            max_len: AtomicU64::new(128),
1633            denied: Default::default(),
1634        }
1635    }
1636}
1637
1638impl Log {
1639    /// Write down a refusal.
1640    ///
1641    /// The counter moves whatever the length allows, which is Redis's order and
1642    /// matters: an operator who turned the log off with `acllog-max-len 0` still
1643    /// gets the numbers in `INFO stats` and only gives up the detail.
1644    fn note(
1645        &self,
1646        server: &Server,
1647        session: &Session,
1648        out: &Out,
1649        reason: Reason,
1650        object: Vec<u8>,
1651        username: Vec<u8>,
1652    ) {
1653        self.denied[reason as usize].fetch_add(1, Relaxed);
1654        let max = self.max_len.load(Relaxed);
1655        let mut held = self.entries.lock().unwrap_or_else(|e| e.into_inner());
1656        if max == 0 {
1657            held.clear();
1658            return;
1659        }
1660        let context = Context::of(session);
1661        let now = server.now_ms();
1662        // The whole `CLIENT INFO` line of the connection, which is what makes
1663        // the log worth reading: the row says a user was refused and this says
1664        // which connection, from which address, running which library. Without
1665        // the newline on the end, which `CLIENT INFO` puts there itself and this
1666        // does not, so the two fields differ by that one byte on a real server
1667        // too.
1668        let mut cinfo = super::client::report(server, session, out.proto(), out.len());
1669        if cinfo.ends_with('\n') {
1670            cinfo.pop();
1671        }
1672        let matched = held.iter().take(SCAN).position(|e| {
1673            e.reason == reason
1674                && e.context == context
1675                && e.object == object
1676                && e.username == username
1677                && now.abs_diff(e.ctime) <= GROUPING_MS
1678        });
1679        if let Some(at) = matched {
1680            // Moved to the front as well as bumped, so the log is in order of
1681            // when something last happened rather than of when it first did.
1682            let mut entry = held.remove(at).expect("the position came from the deque");
1683            entry.cinfo = cinfo;
1684            entry.ctime = now;
1685            entry.count += 1;
1686            held.push_front(entry);
1687            return;
1688        }
1689        held.push_front(Entry {
1690            count: 1,
1691            reason,
1692            context,
1693            object,
1694            username,
1695            ctime: now,
1696            cinfo,
1697            entry_id: self.next_id.fetch_add(1, Relaxed),
1698            created: now,
1699        });
1700        while held.len() as u64 > max {
1701            held.pop_back();
1702        }
1703    }
1704
1705    /// `ACL LOG RESET`.
1706    ///
1707    /// The ids do not go back to nought, which is Redis's behaviour and is the
1708    /// useful one: a tool that remembers the last id it read must not be shown
1709    /// that id again attached to a different refusal.
1710    fn clear(&self) {
1711        self.entries
1712            .lock()
1713            .unwrap_or_else(|e| e.into_inner())
1714            .clear();
1715    }
1716
1717    /// The five numbers `INFO stats` reports, in the order it reports them.
1718    pub(crate) fn counters(&self) -> [u64; 5] {
1719        [
1720            self.denied[Reason::Auth as usize].load(Relaxed),
1721            self.denied[Reason::Command as usize].load(Relaxed),
1722            self.denied[Reason::Key as usize].load(Relaxed),
1723            self.denied[Reason::Channel as usize].load(Relaxed),
1724            // `acl_access_denied_tls_cert`, which cannot move without TLS.
1725            0,
1726        ]
1727    }
1728
1729    /// `acllog-max-len`.
1730    pub(crate) fn max_len(&self) -> u64 {
1731        self.max_len.load(Relaxed)
1732    }
1733
1734    /// `CONFIG SET acllog-max-len`.
1735    ///
1736    /// Nothing is trimmed here, which is the reference's behaviour: lowering the
1737    /// number decides how long the log is allowed to be after the next refusal,
1738    /// and until then `ACL LOG` still reports what is already in it.
1739    pub(crate) fn set_max_len(&self, n: u64) {
1740        self.max_len.store(n, Relaxed);
1741    }
1742}
1743
1744impl Server {
1745    /// The refusals, for the gate and for `ACL LOG`.
1746    pub(crate) fn acl_log(&self) -> &Log {
1747        &self.acllog
1748    }
1749}
1750
1751/// Write down a password that did not get in.
1752///
1753/// Its own function rather than a branch in [`authenticate`], because the two
1754/// things it needs that the rest of that path does not are the command the
1755/// password arrived with and the buffer the reply is going into.
1756fn note_auth(server: &Server, session: &Session, out: &Out, said: &[u8], user: &[u8]) {
1757    yo_alloc::allow(|| {
1758        server.acl_log().note(
1759            server,
1760            session,
1761            out,
1762            Reason::Auth,
1763            // The command as the client spelled it, which is `AUTH` or `HELLO`
1764            // in whatever case it was typed. That is what Redis logs, and it is
1765            // the only way to tell the two apart in the log afterwards.
1766            said.to_vec(),
1767            user.to_vec(),
1768        );
1769    });
1770}
1771
1772// ------------------------------------------------------------- the command
1773
1774/// The subcommands and how many arguments each takes, counting `ACL` itself.
1775///
1776/// A real server gives every one of these a row in the command table with its
1777/// own arity, and enforces it before the body is reached, which is why `ACL
1778/// WHOAMI x` is a wrong number of arguments and `ACL LOG 1 2` is an unknown
1779/// subcommand: the second one passes its own arity check and then falls off the
1780/// end of the parse. There is no subcommand table here yet, so the arities live
1781/// in this list and the same two sentences come out. It folds into D-114.
1782const ARITIES: [(&[u8], i32); 12] = [
1783    (b"cat", -2),
1784    (b"deluser", -3),
1785    (b"dryrun", -4),
1786    (b"genpass", -2),
1787    (b"getuser", 3),
1788    (b"help", 2),
1789    (b"list", 2),
1790    (b"load", 2),
1791    (b"log", -2),
1792    (b"save", 2),
1793    (b"setuser", -3),
1794    (b"users", 2),
1795    // `whoami` is not here because its arity is exactly two, which is the same
1796    // check the fallthrough below makes, and a row for it would be dead weight.
1797];
1798
1799/// `ACL <subcommand> ...`.
1800pub(super) fn execute(
1801    server: &Server,
1802    session: &mut Session,
1803    args: Args<'_>,
1804    out: &mut Out,
1805) -> Result<()> {
1806    let sub = args.get(1);
1807    // The arity of the subcommand before anything else, which is where a real
1808    // server makes this decision: the row is found and checked in
1809    // `processCommand`, so a wrong count is refused before the body runs.
1810    if let Some((name, arity)) = ARITIES
1811        .iter()
1812        .find(|(name, _)| sub.eq_ignore_ascii_case(name))
1813    {
1814        let n = args.len() as i32;
1815        if (*arity > 0 && n != *arity) || (*arity < 0 && n < -*arity) {
1816            let name = std::str::from_utf8(name).unwrap_or("acl");
1817            return Err(args::wrong_arity_sub("acl", name));
1818        }
1819    }
1820
1821    if is(sub, b"whoami") {
1822        if args.len() != 2 {
1823            return Err(args::wrong_arity_sub("acl", "whoami"));
1824        }
1825        out.bulk(session.acl_name());
1826    } else if (is(sub, b"load") || is(sub, b"save")) && server.aclfile().is_none() {
1827        // In front of both bodies and in front of everything either of them
1828        // would check, which is where the reference puts it: a server with no
1829        // ACL file says so and says nothing about what was asked of it.
1830        return Err(Error::new(
1831            Code::Invalid,
1832            "This Redis instance is not configured to use an ACL file. You may want to specify users via the ACL SETUSER command and then issue a CONFIG REWRITE (assuming you have a Redis configuration file set) in order to store users in the Redis configuration.",
1833        ));
1834    } else if is(sub, b"load") {
1835        return yo_alloc::allow(|| load(server, out));
1836    } else if is(sub, b"save") {
1837        return yo_alloc::allow(|| save(server, out));
1838    } else if is(sub, b"log") {
1839        return yo_alloc::allow(|| log(server, args, out));
1840    } else if is(sub, b"cat") {
1841        cat(args, out)?;
1842    } else if is(sub, b"list") {
1843        yo_alloc::allow(|| {
1844            server.users().with(|table| {
1845                out.array(table.len());
1846                for user in table.iter() {
1847                    let mut line = b"user ".to_vec();
1848                    line.extend_from_slice(&user.describe());
1849                    out.bulk(&line);
1850                }
1851                (false, ())
1852            });
1853        });
1854    } else if is(sub, b"users") {
1855        server.users().with(|table| {
1856            out.array(table.len());
1857            for user in table.iter() {
1858                out.bulk(&user.name);
1859            }
1860            (false, ())
1861        });
1862    } else if is(sub, b"getuser") {
1863        yo_alloc::allow(|| getuser(server, args, out));
1864    } else if is(sub, b"setuser") {
1865        return yo_alloc::allow(|| setuser(server, args, out));
1866    } else if is(sub, b"deluser") {
1867        return deluser(server, args, out);
1868    } else if is(sub, b"genpass") {
1869        genpass(args, out)?;
1870    } else if is(sub, b"dryrun") {
1871        return yo_alloc::allow(|| dryrun(server, args, out));
1872    } else if is(sub, b"help") {
1873        super::server::help(out, HELP);
1874    } else {
1875        return Err(args::unknown_subcommand(sub, "ACL"));
1876    }
1877    Ok(())
1878}
1879
1880/// `ACL CAT` and `ACL CAT <category>`.
1881///
1882/// With no argument this is the list of categories, and with one it is the
1883/// commands in it, in table order. Redis walks a hash table there and so reports
1884/// an order that is neither sorted nor stable across versions, so matching it
1885/// exactly is not a thing to aim at; what a client can rely on is the set, and
1886/// this reports the same set for every category the two servers share.
1887fn cat(args: Args<'_>, out: &mut Out) -> Result<()> {
1888    if args.len() == 2 {
1889        out.array(CATEGORIES.len());
1890        for name in CATEGORIES {
1891            out.bulk(name.as_bytes());
1892        }
1893        return Ok(());
1894    }
1895    if args.len() > 3 {
1896        return Err(args::subcommand_syntax(args.get(1), "ACL"));
1897    }
1898    let Some(wanted) = category(args.get(2)) else {
1899        return Err(yo_alloc::allow(|| {
1900            Error::fmt(
1901                Code::Invalid,
1902                format_args!(
1903                    "Unknown category '{}'",
1904                    String::from_utf8_lossy(args.get(2))
1905                ),
1906            )
1907        }));
1908    };
1909    // The header goes on afterwards, because how many commands are in a
1910    // category is not a thing the table can be asked without walking it.
1911    let start = out.len();
1912    let mut n = 0;
1913    for spec in table::COMMANDS {
1914        if spec.acl.iter().any(|held| &held[1..] == wanted) {
1915            out.bulk(spec.name.as_bytes());
1916            n += 1;
1917        }
1918    }
1919    out.close_array(start, n);
1920    Ok(())
1921}
1922
1923/// `ACL GETUSER <username>`.
1924fn getuser(server: &Server, args: Args<'_>, out: &mut Out) {
1925    let Some(user) = server.users().get(args.get(2)) else {
1926        out.nil();
1927        return;
1928    };
1929    // Six fields: the two about the account, the root selector's three repeated
1930    // at the top level for the clients that were written before selectors
1931    // existed, and the selectors themselves.
1932    out.map(6);
1933    out.bulk(b"flags");
1934    // Only the flags that belong to the account. The selector flags are named in
1935    // the same table and an older server did list them here, but 8.10.1 does not,
1936    // and a client that wants to know whether a user can reach every key reads
1937    // the keys field rather than counting words in this set.
1938    let flags = user.flags();
1939    let root = &user.selectors[0];
1940    out.set(flags.len());
1941    for flag in &flags {
1942        out.bulk(flag.as_bytes());
1943    }
1944    out.bulk(b"passwords");
1945    out.array(user.passwords.len());
1946    for hash in &user.passwords {
1947        out.bulk(hash);
1948    }
1949    describe_selector(root, out);
1950    out.bulk(b"selectors");
1951    out.array(user.selectors.len() - 1);
1952    for selector in &user.selectors[1..] {
1953        out.map(3);
1954        describe_selector(selector, out);
1955    }
1956}
1957
1958/// The three fields a selector contributes to `ACL GETUSER`.
1959fn describe_selector(selector: &Selector, out: &mut Out) {
1960    out.bulk(b"commands");
1961    out.bulk(&selector.describe_commands());
1962    out.bulk(b"keys");
1963    out.bulk(&selector.describe_keys());
1964    out.bulk(b"channels");
1965    out.bulk(&selector.describe_channels());
1966}
1967
1968/// `ACL SETUSER <username> [rule ...]`.
1969///
1970/// Every rule is applied to a copy and the copy replaces the user only if all of
1971/// them worked, so a `SETUSER` that fails halfway leaves nothing behind. That is
1972/// worth more than it sounds: the failure case is an operator tightening a
1973/// user's permissions and mistyping one rule, and a server that applied the
1974/// first half would have left the user with the new restrictions and none of the
1975/// new grants.
1976fn setuser(server: &Server, args: Args<'_>, out: &mut Out) -> Result<()> {
1977    let name = args.get(2);
1978    if name.contains(&b' ') || name.contains(&0) {
1979        return Err(Error::new(
1980            Code::Invalid,
1981            "Usernames can't contain spaces or null characters",
1982        ));
1983    }
1984    let rules = merge(args, 3)?;
1985    let open = server.users().open_channels();
1986    let outcome = server.users().with(|table| {
1987        let at = table.binary_search_by(|u| u.name.as_slice().cmp(name));
1988        let mut staged = match at {
1989            Ok(at) => table[at].clone(),
1990            Err(_) => User::new(name, open),
1991        };
1992        for rule in &rules {
1993            if let Err(why) = set_user(&mut staged, rule, open) {
1994                return (false, Err((rule.clone(), why)));
1995            }
1996        }
1997        match at {
1998            Ok(at) => table[at] = staged,
1999            Err(at) => table.insert(at, staged),
2000        }
2001        (true, Ok(()))
2002    });
2003    match outcome {
2004        Ok(()) => {
2005            out.ok();
2006            Ok(())
2007        }
2008        Err((rule, why)) => Err(Error::fmt(
2009            Code::Invalid,
2010            format_args!(
2011                "Error in ACL SETUSER modifier '{}': {}",
2012                String::from_utf8_lossy(&rule),
2013                why.text()
2014            ),
2015        )),
2016    }
2017}
2018
2019/// Join the arguments from `from` on, gluing a selector back together.
2020///
2021/// A selector is one rule and a client sends it as several arguments, because
2022/// `(+get ~k:*)` has a space in it and the wire has no way to say that was meant
2023/// as one word. So a `(` that does not end in `)` swallows the arguments after
2024/// it until one does. An opening bracket that is never closed is the one rule
2025/// error reported in its own sentence rather than as a modifier error, because
2026/// there is no single modifier to blame.
2027fn merge(args: Args<'_>, from: usize) -> Result<Vec<Vec<u8>>> {
2028    let words = (from..args.len()).map(|i| args.get(i));
2029    glue(words).map_err(|at| {
2030        Error::fmt(
2031            Code::Invalid,
2032            format_args!(
2033                "Unmatched parenthesis in acl selector starting at '{}'.",
2034                String::from_utf8_lossy(args.get(from + at))
2035            ),
2036        )
2037    })
2038}
2039
2040/// The half of [`merge`] the ACL file wants too, which is everything but the
2041/// sentence.
2042///
2043/// The error is which word opened the bracket that was never closed, because the
2044/// two callers name it differently: the command quotes the word and the file
2045/// gives the line it was on.
2046fn glue<'a>(words: impl Iterator<Item = &'a [u8]>) -> std::result::Result<Vec<Vec<u8>>, usize> {
2047    let mut out: Vec<Vec<u8>> = Vec::new();
2048    let mut open: Option<usize> = None;
2049    for (i, word) in words.enumerate() {
2050        if open.is_none() && word.first() == Some(&b'(') && word.last() != Some(&b')') {
2051            open = Some(i);
2052            out.push(word.to_vec());
2053            continue;
2054        }
2055        if open.is_some() {
2056            let held = out.last_mut().expect("an open bracket left a rule behind");
2057            held.push(b' ');
2058            held.extend_from_slice(word);
2059            if word.last() == Some(&b')') {
2060                open = None;
2061            }
2062            continue;
2063        }
2064        out.push(word.to_vec());
2065    }
2066    match open {
2067        Some(at) => Err(at),
2068        None => Ok(out),
2069    }
2070}
2071
2072/// `ACL DELUSER <username> [<username> ...]`.
2073///
2074/// The default user is checked for over the whole list before anything is
2075/// deleted, so `ACL DELUSER alice default` deletes neither. Redis does the same
2076/// and the reason is the same as `SETUSER`'s: a half done change to who may
2077/// reach a server is worse than no change.
2078fn deluser(server: &Server, args: Args<'_>, out: &mut Out) -> Result<()> {
2079    for i in 2..args.len() {
2080        if args.get(i) == DEFAULT {
2081            return Err(Error::new(
2082                Code::Invalid,
2083                "The 'default' user cannot be removed",
2084            ));
2085        }
2086    }
2087    let gone = server.users().with(|table| {
2088        let mut gone = 0;
2089        for i in 2..args.len() {
2090            if let Ok(at) = table.binary_search_by(|u| u.name.as_slice().cmp(args.get(i))) {
2091                table.remove(at);
2092                gone += 1;
2093            }
2094        }
2095        (gone > 0, gone)
2096    });
2097    out.int(gone);
2098    Ok(())
2099}
2100
2101/// `ACL GENPASS [<bits>]`.
2102///
2103/// The bytes come from the operating system rather than from the engine's own
2104/// generator, which is seeded and reproducible on purpose. See
2105/// [`yo_common::entropy`].
2106fn genpass(args: Args<'_>, out: &mut Out) -> Result<()> {
2107    if args.len() > 3 {
2108        return Err(args::subcommand_syntax(args.get(1), "ACL"));
2109    }
2110    let bits = if args.len() == 3 { args.int(2)? } else { 256 };
2111    if bits <= 0 || bits > 4096 {
2112        return Err(Error::new(
2113            Code::Invalid,
2114            "ACL GENPASS argument must be the number of bits for the output password, a positive number up to 4096",
2115        ));
2116    }
2117    // One hex character is four bits, rounded up, so `GENPASS 10` is three
2118    // characters and holds twelve bits rather than ten. That is the reference's
2119    // arithmetic and it errs towards more entropy than was asked for.
2120    let chars = ((bits + 3) / 4) as usize;
2121    let mut raw = [0u8; 4096 / 8 + 1];
2122    let bytes = chars.div_ceil(2);
2123    yo_common::entropy::fill(&mut raw[..bytes]);
2124    const DIGITS: &[u8; 16] = b"0123456789abcdef";
2125    let mut hex = [0u8; 1024];
2126    for (i, slot) in hex[..chars].iter_mut().enumerate() {
2127        let byte = raw[i / 2];
2128        *slot = DIGITS[usize::from(if i % 2 == 0 { byte >> 4 } else { byte & 0xf })];
2129    }
2130    out.bulk(&hex[..chars]);
2131    Ok(())
2132}
2133
2134/// `ACL DRYRUN <username> <command> [<arg> ...]`.
2135///
2136/// The same question the gate asks, asked out loud. The answer is a bulk string
2137/// rather than an error even when it is a refusal, because the command
2138/// succeeded: it was asked whether something would be allowed and it found out.
2139fn dryrun(server: &Server, args: Args<'_>, out: &mut Out) -> Result<()> {
2140    let Some(user) = server.users().get(args.get(2)) else {
2141        return Err(Error::fmt(
2142            Code::Invalid,
2143            format_args!("User '{}' not found", String::from_utf8_lossy(args.get(2))),
2144        ));
2145    };
2146    let Some(spec) = table::lookup(args.get(3)) else {
2147        return Err(Error::fmt(
2148            Code::Invalid,
2149            format_args!(
2150                "Command '{}' not found",
2151                String::from_utf8_lossy(args.get(3))
2152            ),
2153        ));
2154    };
2155    if !table::arity_ok(spec, args.len() - 3) {
2156        return Err(args::wrong_arity(spec.name));
2157    }
2158    match permits(&user, spec, args, 3) {
2159        Ok(()) => out.ok(),
2160        Err(why) => {
2161            let text = refusal(why, &user.name, spec, args, 3, true);
2162            out.bulk(text.as_bytes());
2163        }
2164    }
2165    Ok(())
2166}
2167
2168/// `ACL LOG [<count> | RESET]`.
2169fn log(server: &Server, args: Args<'_>, out: &mut Out) -> Result<()> {
2170    // Two arguments or three and nothing else, and a fourth is an unknown
2171    // subcommand rather than a wrong count. That reads like a mistake and is
2172    // not: a real server checks `acl|log`'s own arity first, which is a minimum
2173    // of two and lets `ACL LOG 1 2` through, and then falls off the end of a
2174    // parse that only knows two shapes.
2175    if args.len() > 3 {
2176        return Err(args::subcommand_syntax(args.get(1), "ACL"));
2177    }
2178    if args.len() == 3 && is(args.get(2), b"reset") {
2179        server.acl_log().clear();
2180        out.ok();
2181        return Ok(());
2182    }
2183    // Ten by default, and a negative count is nought rather than an error, which
2184    // is what makes `ACL LOG -1` an empty array.
2185    let wanted = if args.len() == 3 {
2186        args.int(2)?.max(0)
2187    } else {
2188        10
2189    };
2190    let now = server.now_ms();
2191    let held = server
2192        .acl_log()
2193        .entries
2194        .lock()
2195        .unwrap_or_else(|e| e.into_inner());
2196    let count = (wanted as u64).min(held.len() as u64) as usize;
2197    out.array(count);
2198    for entry in held.iter().take(count) {
2199        out.map(10);
2200        out.bulk(b"count");
2201        out.int(entry.count as i64);
2202        out.bulk(b"reason");
2203        out.bulk(entry.reason.name().as_bytes());
2204        out.bulk(b"context");
2205        out.bulk(entry.context.name().as_bytes());
2206        out.bulk(b"object");
2207        out.bulk(&entry.object);
2208        out.bulk(b"username");
2209        out.bulk(&entry.username);
2210        out.bulk(b"age-seconds");
2211        // Seconds with the milliseconds after the point, which is the one field
2212        // here that is a double, and it is the age now rather than the age when
2213        // the refusal happened.
2214        out.double(now.saturating_sub(entry.ctime) as f64 / 1000.0);
2215        out.bulk(b"client-info");
2216        out.bulk(entry.cinfo.as_bytes());
2217        out.bulk(b"entry-id");
2218        out.int(entry.entry_id as i64);
2219        out.bulk(b"timestamp-created");
2220        out.int(entry.created as i64);
2221        out.bulk(b"timestamp-last-updated");
2222        out.int(entry.ctime as i64);
2223    }
2224    Ok(())
2225}
2226
2227/// `ACL SAVE`, which writes the users out in the format the file is read in.
2228///
2229/// A temporary file beside the real one, then fsync, then rename, then fsync of
2230/// the directory. That is Redis's sequence and it is the only sequence that
2231/// leaves a reader with either the whole old file or the whole new one: a write
2232/// straight over the real file would leave a half written ACL on a server that
2233/// lost power, and a server that came back up refusing to let anybody in is a
2234/// worse outcome than one that came back up with yesterday's users.
2235fn save(server: &Server, out: &mut Out) -> Result<()> {
2236    let Some(path) = server.aclfile() else {
2237        return Ok(());
2238    };
2239    let mut text = Vec::with_capacity(256);
2240    server.users().with(|table| {
2241        for user in table.iter() {
2242            text.extend_from_slice(b"user ");
2243            text.extend_from_slice(&user.describe());
2244            text.push(b'\n');
2245        }
2246        (false, ())
2247    });
2248    if write_file(path, &text).is_err() {
2249        // The reason is in the server log on a real server and there is nowhere
2250        // to put it here, so the client gets the sentence and nothing else,
2251        // which is also what a real server gives it.
2252        return Err(Error::new(
2253            Code::Invalid,
2254            "There was an error trying to save the ACLs. Please check the server logs for more information",
2255        ));
2256    }
2257    out.ok();
2258    Ok(())
2259}
2260
2261/// The write, the rename and the two syncs.
2262fn write_file(path: &Path, text: &[u8]) -> std::io::Result<()> {
2263    use std::io::Write as _;
2264
2265    let temp = path.with_file_name(format!(
2266        "{}.tmp-{}-{}",
2267        path.file_name().unwrap_or_default().to_string_lossy(),
2268        std::process::id(),
2269        std::time::SystemTime::now()
2270            .duration_since(std::time::UNIX_EPOCH)
2271            .map_or(0, |d| d.as_millis())
2272    ));
2273    let outcome = (|| {
2274        let mut file = std::fs::File::create(&temp)?;
2275        file.write_all(text)?;
2276        file.sync_all()?;
2277        drop(file);
2278        std::fs::rename(&temp, path)
2279    })();
2280    if outcome.is_err() {
2281        let _ = std::fs::remove_file(&temp);
2282        return outcome;
2283    }
2284    // The directory, so that the rename itself is on disk and not only the
2285    // bytes the rename pointed at. Best effort, because opening a directory as a
2286    // file is a Unix thing and Windows answers an error rather than a handle,
2287    // and a failure here means the file is written and the entry naming it may
2288    // not have reached the platter yet.
2289    if let Some(dir) = path.parent().filter(|d| !d.as_os_str().is_empty())
2290        && let Ok(handle) = std::fs::File::open(dir)
2291    {
2292        let _ = handle.sync_all();
2293    }
2294    Ok(())
2295}
2296
2297/// `ACL LOAD`, which replaces every user with what the file says.
2298fn load(server: &Server, out: &mut Out) -> Result<()> {
2299    let Some(path) = server.aclfile() else {
2300        return Ok(());
2301    };
2302    match load_file(server, path) {
2303        Ok(()) => {
2304            out.ok();
2305            Ok(())
2306        }
2307        Err(errors) => Err(Error::fmt(Code::Invalid, format_args!("{errors}"))),
2308    }
2309}
2310
2311/// Read `path` and, if every line of it is good, make it the server's users.
2312///
2313/// The whole file or none of it. The users are built in a table of their own and
2314/// only swapped in once the last line has parsed, which is the reference's
2315/// design and is the only sane one: a file with a typo halfway down would
2316/// otherwise leave a server holding half of the new ACL and half of the old one,
2317/// and nobody could say which half.
2318///
2319/// # Errors
2320///
2321/// Every complaint the file raised, joined into one sentence, which is what the
2322/// client gets and what a server that cannot start prints.
2323pub(crate) fn load_file(server: &Server, path: &Path) -> std::result::Result<(), String> {
2324    let name = path.display().to_string();
2325    let text = match std::fs::read(path) {
2326        Ok(text) => text,
2327        Err(e) => {
2328            return Err(format!(
2329                "Error loading ACLs, opening file '{name}': {}",
2330                because(&e)
2331            ));
2332        }
2333    };
2334    let mut errors = String::new();
2335    let mut staged: Vec<User> = Vec::new();
2336    let open = server.users().open_channels();
2337    for (at, raw) in text.split(|b| *b == b'\n').enumerate() {
2338        let line = trim(raw);
2339        // Blank lines and comments, which is what lets a file be commented.
2340        if line.is_empty() || line[0] == b'#' {
2341            continue;
2342        }
2343        let linenum = at + 1;
2344        let words: Vec<&[u8]> = line.split(|b| *b == b' ').collect();
2345        if words[0] != b"user" || words.len() < 2 {
2346            errors.push_str(&format!(
2347                "{name}:{linenum} should start with user keyword followed by the username. "
2348            ));
2349            continue;
2350        }
2351        // A username with a space in it could not be read back, since this is
2352        // where the reading happens and it splits on spaces. A tab or a null is
2353        // refused for the same reason a space is: the file is the only place a
2354        // user is written down and a name that cannot be written down is a user
2355        // that cannot be got at again.
2356        let who = words[1];
2357        if who.iter().any(|b| b.is_ascii_whitespace() || *b == 0) {
2358            errors.push_str(&format!(
2359                "'{name}:{linenum}: username '{}' contains invalid characters. ",
2360                String::from_utf8_lossy(who)
2361            ));
2362            continue;
2363        }
2364        if staged.iter().any(|u| u.name == who) {
2365            errors.push_str(&format!(
2366                "WARNING: Duplicate user '{}' found on line {linenum}. ",
2367                String::from_utf8_lossy(who)
2368            ));
2369            continue;
2370        }
2371        let Ok(rules) = glue(words[2..].iter().copied()) else {
2372            errors.push_str(&format!(
2373                "{name}:{linenum}: Unmatched parenthesis in selector definition."
2374            ));
2375            continue;
2376        };
2377        let mut user = User::new(who, open);
2378        let mut said = false;
2379        for rule in &rules {
2380            let Err(why) = set_user(&mut user, trim(rule), open) else {
2381                continue;
2382            };
2383            if why == Bad::Unknown {
2384                // A name nobody has heard of is quoted back, because a command
2385                // name is not a secret and an operator staring at a file wants
2386                // to know which word was wrong. Every other complaint is about
2387                // the shape of a rule that may hold a password hash.
2388                errors.push_str(&format!(
2389                    "{name}:{linenum}: Error in applying operation '{}': {}. ",
2390                    String::from_utf8_lossy(rule),
2391                    why.text()
2392                ));
2393            } else if !said {
2394                // Only the first of the others, because a rule that failed may
2395                // have been what the rules after it were written against, and
2396                // eight complaints about one mistake is worse than one.
2397                errors.push_str(&format!("{name}:{linenum}: {}. ", why.text()));
2398                said = true;
2399            }
2400        }
2401        staged.push(user);
2402    }
2403    if !errors.is_empty() {
2404        errors.push_str(
2405            "WARNING: ACL errors detected, no change to the previously active ACL rules was performed",
2406        );
2407        return Err(errors);
2408    }
2409    staged.sort_by(|a, b| a.name.cmp(&b.name));
2410    server.users().with(|table| {
2411        *table = staged;
2412        // A file with no default user in it still leaves the server with one,
2413        // because there has to be a user for a connection that has not
2414        // authenticated to be. The reference gets there by a different route,
2415        // making a fresh default and copying it over the old one, and lands on
2416        // the same user.
2417        if let Err(at) = table.binary_search_by(|u| u.name.as_slice().cmp(DEFAULT)) {
2418            table.insert(at, User::default_user());
2419        }
2420        (true, ())
2421    });
2422    Ok(())
2423}
2424
2425/// What went wrong with a file, in the words the C library would have used.
2426///
2427/// Rust writes `No such file or directory (os error 2)` where C's `strerror`
2428/// writes `No such file or directory`, and the sentence this ends up in is one
2429/// a client may be matching on, so the number Rust adds comes back off.
2430fn because(e: &std::io::Error) -> String {
2431    let said = e.to_string();
2432    match said.find(" (os error ") {
2433        Some(at) => said[..at].to_string(),
2434        None => said,
2435    }
2436}
2437
2438/// A line with the blanks taken off both ends.
2439///
2440/// The same four characters the reference trims, which is why a file written on
2441/// Windows loads: the carriage return at the end of every line is one of them.
2442fn trim(line: &[u8]) -> &[u8] {
2443    let blank = |b: &u8| matches!(b, b' ' | b'\t' | b'\r' | b'\n');
2444    let from = line.iter().position(|b| !blank(b)).unwrap_or(line.len());
2445    let to = line.iter().rposition(|b| !blank(b)).map_or(from, |i| i + 1);
2446    &line[from..to]
2447}
2448
2449/// What `ACL HELP` says.
2450const HELP: &[&str] = &[
2451    "ACL <subcommand> [<arg> [value] [opt] ...]. Subcommands are:",
2452    "CAT [<category>]",
2453    "    List all commands that belong to <category>, or all command categories",
2454    "    when no category is specified.",
2455    "DELUSER <username> [<username> ...]",
2456    "    Delete a list of users.",
2457    "DRYRUN <username> <command> [<arg> ...]",
2458    "    Returns whether the user can execute the given command without executing the command.",
2459    "GETUSER <username>",
2460    "    Get the user's details.",
2461    "GENPASS [<bits>]",
2462    "    Generate a secure 256-bit user password. The optional `bits` argument can",
2463    "    be used to specify a different size.",
2464    "LIST",
2465    "    Show users details in config file format.",
2466    "LOAD",
2467    "    Reload users from the ACL file.",
2468    "LOG [<count> | RESET]",
2469    "    Show the ACL log entries.",
2470    "SAVE",
2471    "    Save the current config to the ACL file.",
2472    "SETUSER <username> <attribute> [<attribute> ...]",
2473    "    Create or modify a user with the specified attributes.",
2474    "USERS",
2475    "    List all the registered usernames.",
2476    "WHOAMI",
2477    "    Return the current connection username.",
2478    "HELP",
2479    "    Print this help.",
2480];
2481
2482#[cfg(test)]
2483mod tests {
2484    use super::*;
2485    use crate::proto::Limits;
2486    use crate::request::{Argv, Step};
2487
2488    /// A user built by applying rules in order, or the first rule that failed.
2489    fn built(rules: &[&str]) -> std::result::Result<User, Bad> {
2490        let mut user = User::new(b"u", false);
2491        for rule in rules {
2492            set_user(&mut user, rule.as_bytes(), false)?;
2493        }
2494        Ok(user)
2495    }
2496
2497    /// The user's line the way `ACL LIST` writes it.
2498    fn listed(rules: &[&str]) -> String {
2499        let user = built(rules).expect("every rule here is a good one");
2500        String::from_utf8(user.describe()).expect("the description is text")
2501    }
2502
2503    /// Whether this user could run this command, said as a word so a failing
2504    /// assertion reads like the question it was asked.
2505    fn allows(rules: &[&str], words: &[&str]) -> bool {
2506        let user = built(rules).expect("every rule here is a good one");
2507        let mut buf = format!("*{}\r\n", words.len()).into_bytes();
2508        for word in words {
2509            buf.extend_from_slice(format!("${}\r\n{word}\r\n", word.len()).as_bytes());
2510        }
2511        let mut argv = Argv::new();
2512        let Ok(Step::Command { .. }) = argv.decode(&buf, &Limits::default()) else {
2513            panic!("the test wrote a command that does not decode");
2514        };
2515        let args = Args::new(&argv, &buf);
2516        let spec = table::lookup(args.name()).expect("a command this server has");
2517        permits(&user, spec, args, 0).is_ok()
2518    }
2519
2520    #[test]
2521    fn a_key_permission_with_no_pattern_after_it_is_an_empty_pattern() {
2522        // 8.10.1 takes a bare `%R` and writes it back with the `~` it never got.
2523        assert_eq!(
2524            listed(&["%R"]),
2525            "u off sanitize-payload %R~ resetchannels -@all"
2526        );
2527        assert_eq!(
2528            listed(&["%RW"]),
2529            "u off sanitize-payload ~ resetchannels -@all"
2530        );
2531    }
2532
2533    #[test]
2534    fn a_key_permission_that_is_not_r_or_w_once_each_is_a_syntax_error() {
2535        assert_eq!(built(&["%"]).err(), Some(Bad::Syntax));
2536        assert_eq!(built(&["%~k:*"]).err(), Some(Bad::Syntax));
2537        assert_eq!(built(&["%RR~k:*"]).err(), Some(Bad::Syntax));
2538        assert_eq!(built(&["%X~k:*"]).err(), Some(Bad::Syntax));
2539    }
2540
2541    #[test]
2542    fn a_subcommand_can_be_taken_back_off_a_container_that_was_allowed() {
2543        assert_eq!(
2544            listed(&["+config", "-config|get"]),
2545            "u off sanitize-payload resetchannels -@all +config -config|get"
2546        );
2547        assert!(allows(
2548            &["+config", "-config|get"],
2549            &["config", "set", "maxmemory", "0"]
2550        ));
2551        assert!(!allows(
2552            &["+config", "-config|get"],
2553            &["config", "get", "maxmemory"]
2554        ));
2555        // And allowing the container again forgets the exception, the same way
2556        // allowing it forgets a first argument it was limited to.
2557        assert_eq!(
2558            listed(&["+config", "-config|get", "+config"]),
2559            "u off sanitize-payload resetchannels -@all +config"
2560        );
2561        assert!(allows(
2562            &["+config", "-config|get", "+config"],
2563            &["config", "get", "maxmemory"]
2564        ));
2565    }
2566
2567    #[test]
2568    fn a_first_argument_can_only_be_taken_off_a_command_that_has_subcommands() {
2569        // GET has no subcommands, so a real server looks up `get|nope`, finds
2570        // nothing, and says so. Allowing one is a different mechanism and works.
2571        assert_eq!(built(&["-get|nope"]).err(), Some(Bad::Unknown));
2572        assert_eq!(built(&["-select|0"]).err(), Some(Bad::Unknown));
2573        assert!(built(&["+get|nope"]).is_ok());
2574    }
2575
2576    #[test]
2577    fn a_category_a_subcommand_holds_reaches_that_subcommand_and_no_further() {
2578        // CONFIG is only `@slow` in the table because 8.10.1 puts `@admin` on
2579        // `config|get` and `config|set` rather than on `config`, so a rule about
2580        // `@admin` has to take CONFIG GET away and leave CONFIG HELP behind.
2581        let deny = ["~*", "+@all", "-@admin"];
2582        assert!(!allows(&deny, &["config", "get", "maxmemory"]));
2583        assert!(allows(&deny, &["config", "help"]));
2584        assert!(!allows(&deny, &["client", "kill", "id", "4"]));
2585        assert!(allows(&deny, &["client", "setname", "x"]));
2586        assert!(allows(&deny, &["acl", "whoami"]));
2587        assert!(!allows(&deny, &["acl", "setuser", "u"]));
2588        // And the other way round, from a user that starts with nothing.
2589        let grant = ["~*", "-@all", "+@admin"];
2590        assert!(allows(&grant, &["config", "get", "maxmemory"]));
2591        assert!(!allows(&grant, &["config", "help"]));
2592        assert!(!allows(&grant, &["get", "k"]));
2593        // None of which changes what the container is listed as being in, or
2594        // `COMMAND INFO config` and `ACL CAT admin` would both start lying.
2595        let config = table::lookup(b"config").expect("a command this server has");
2596        assert_eq!(config.acl, ["@slow"]);
2597    }
2598
2599    #[test]
2600    fn the_flags_a_user_reports_are_the_ones_about_the_account() {
2601        // Not `allkeys`, which an older server did report here and 8.10.1 does
2602        // not. A client that wants to know reads the keys field.
2603        let user = built(&["on", "~*", "&*", "+@all"]).expect("good rules");
2604        assert_eq!(user.flags(), ["on", "sanitize-payload"]);
2605    }
2606
2607    #[test]
2608    fn a_line_of_the_file_loses_the_blanks_on_both_ends() {
2609        assert_eq!(trim(b"  user alice  "), b"user alice");
2610        assert_eq!(trim(b"user alice\r"), b"user alice");
2611        assert_eq!(trim(b"\t\r\n "), b"");
2612        assert_eq!(trim(b""), b"");
2613        // Only the ends, because a rule is separated from the next one by a
2614        // single space and taking the inner ones out would join two rules.
2615        assert_eq!(trim(b" a  b "), b"a  b");
2616    }
2617
2618    #[test]
2619    fn the_reason_a_file_would_not_open_reads_the_way_c_writes_it() {
2620        let missing = std::io::Error::from_raw_os_error(2);
2621        // What is claimed here is that the number Rust puts on the end comes
2622        // off, and that is true everywhere. The sentence in front of it is the
2623        // system's own and is not: errno 2 is `No such file or directory` out
2624        // of a C library and `The system cannot find the file specified.` out
2625        // of Windows, and a server built on Windows should say what Windows
2626        // says rather than repeat a sentence from another operating system.
2627        let said = because(&missing);
2628        assert!(!said.contains("(os error"), "{said}");
2629        assert!(missing.to_string().starts_with(&said), "{said}");
2630        #[cfg(unix)]
2631        assert_eq!(said, "No such file or directory");
2632        // Anything with no errno behind it is left alone, since there is no
2633        // number on the end of it to take off.
2634        let made_up = std::io::Error::other("something else");
2635        assert_eq!(because(&made_up), "something else");
2636    }
2637
2638    #[test]
2639    fn a_new_selector_starts_where_acl_pubsub_default_says() {
2640        let mut open = User::new(b"u", true);
2641        set_user(&mut open, b"on", true).expect("a good rule");
2642        assert!(String::from_utf8_lossy(&open.describe()).contains("&*"));
2643        // And a reset goes back to the same place rather than to the built in
2644        // one, which is what makes the setting worth having.
2645        set_user(&mut open, b"reset", true).expect("a good rule");
2646        assert!(String::from_utf8_lossy(&open.describe()).contains("&*"));
2647        let shut = User::new(b"u", false);
2648        assert!(String::from_utf8_lossy(&shut.describe()).contains("resetchannels"));
2649    }
2650}