Fix duration overflow, expiry-warned resets, DICT control-char injection, and HostServ TAKE forbid recheck
This commit is contained in:
parent
68df1e7078
commit
d833dc28f0
5 changed files with 20 additions and 2 deletions
|
|
@ -2354,7 +2354,9 @@ pub fn parse_duration(s: &str) -> Option<u64> {
|
||||||
c if c.is_ascii_digit() => (s, 1),
|
c if c.is_ascii_digit() => (s, 1),
|
||||||
_ => return None,
|
_ => return None,
|
||||||
};
|
};
|
||||||
num.parse::<u64>().ok().map(|n| n * mult)
|
// Checked so an out-of-range duration is rejected (None) rather than wrapping to
|
||||||
|
// a garbage/near-instant expiry in a release build.
|
||||||
|
num.parse::<u64>().ok().and_then(|n| n.checked_mul(mult))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Case-insensitive hostmask glob: `*` (any run) and `?` (one char).
|
// Case-insensitive hostmask glob: `*` (any run) and `?` (one char).
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,12 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db:
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
// A menu offer can outlive a later FORBID; re-check at grant time like the
|
||||||
|
// request/approve/default paths, or a stale offer bypasses the impersonation guard.
|
||||||
|
if db.vhost_is_forbidden(&host) {
|
||||||
|
ctx.notice(me, from.uid, format!("\x02{host}\x02 isn't allowed here. Please choose another."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
match db.set_vhost(account, &host, "offer", None) {
|
match db.set_vhost(account, &host, "offer", None) {
|
||||||
Ok(()) => {
|
Ok(()) => {
|
||||||
ctx.apply_vhost(from.uid, &host);
|
ctx.apply_vhost(from.uid, &host);
|
||||||
|
|
|
||||||
|
|
@ -96,7 +96,10 @@ fn parse_definition(raw: &str) -> Option<String> {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let body: String = body.split_whitespace().collect::<Vec<_>>().join(" ");
|
// Drop control characters an untrusted/MITM'd DICT server could embed (CTCP
|
||||||
|
// \x01, color/format \x02\x03, NUL truncation) before this is spoken into a
|
||||||
|
// channel. split_whitespace already removed CR/LF/TAB.
|
||||||
|
let body: String = body.split_whitespace().collect::<Vec<_>>().join(" ").chars().filter(|c| !c.is_control()).collect();
|
||||||
if body.is_empty() {
|
if body.is_empty() {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -734,6 +734,7 @@ impl Db {
|
||||||
let _ = self.log.append(Event::AccountSeen { account: account.to_string(), ts: now });
|
let _ = self.log.append(Event::AccountSeen { account: account.to_string(), ts: now });
|
||||||
if let Some(a) = self.accounts.get_mut(&k) {
|
if let Some(a) = self.accounts.get_mut(&k) {
|
||||||
a.last_seen = a.last_seen.max(now);
|
a.last_seen = a.last_seen.max(now);
|
||||||
|
a.expiry_warned = false; // mirror the AccountSeen fold, or a re-active account never re-warns
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -747,6 +748,7 @@ impl Db {
|
||||||
let _ = self.log.append(Event::ChannelUsed { channel: channel.to_string(), ts: now });
|
let _ = self.log.append(Event::ChannelUsed { channel: channel.to_string(), ts: now });
|
||||||
if let Some(c) = self.channels.get_mut(&k) {
|
if let Some(c) = self.channels.get_mut(&k) {
|
||||||
c.last_used = c.last_used.max(now);
|
c.last_used = c.last_used.max(now);
|
||||||
|
c.expiry_warned = false; // mirror the ChannelUsed fold, or a re-active channel never re-warns
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1258,6 +1258,11 @@ impl Db {
|
||||||
if c.noexpire {
|
if c.noexpire {
|
||||||
snapshot.push(Event::ChannelNoExpire { channel: c.name.clone(), on: true });
|
snapshot.push(Event::ChannelNoExpire { channel: c.name.clone(), on: true });
|
||||||
}
|
}
|
||||||
|
if c.expiry_warned {
|
||||||
|
// Preserve the "warning already sent" flag, or compaction re-arms it
|
||||||
|
// and the idle channel is warned again on the next sweep.
|
||||||
|
snapshot.push(Event::ChannelExpiryWarned { channel: c.name.clone() });
|
||||||
|
}
|
||||||
if c.settings != ChanSettings::default() {
|
if c.settings != ChanSettings::default() {
|
||||||
snapshot.push(Event::ChannelSettingsSet { channel: c.name.clone(), settings: c.settings });
|
snapshot.push(Event::ChannelSettingsSet { channel: c.name.clone(), settings: c.settings });
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue