rpc: authenticate on the header block before buffering the request body — the httpd read up to MAX_REQUEST (256 KiB) and only then checked the Authorization header, so an unauthenticated peer reaching rpc_bind could make each connection consume memory + a worker thread; now POST+auth are validated as soon as headers are complete, before any body is read
This commit is contained in:
parent
5b33786a46
commit
a218d65371
1 changed files with 22 additions and 23 deletions
|
|
@ -79,6 +79,27 @@ fn handle(
|
||||||
if let Some(pos) = find_headers_end(&buf) {
|
if let Some(pos) = find_headers_end(&buf) {
|
||||||
head_end = Some(pos);
|
head_end = Some(pos);
|
||||||
framing = framing_of(&buf[..pos]);
|
framing = framing_of(&buf[..pos]);
|
||||||
|
// Authenticate on the header block alone, before buffering any body,
|
||||||
|
// so an unauthenticated peer can't stream up to MAX_REQUEST at us.
|
||||||
|
let header_text = String::from_utf8_lossy(&buf[..pos]).into_owned();
|
||||||
|
let first = header_text.lines().next().unwrap_or("");
|
||||||
|
if !first
|
||||||
|
.split_whitespace()
|
||||||
|
.next()
|
||||||
|
.is_some_and(|m| m.eq_ignore_ascii_case("POST"))
|
||||||
|
{
|
||||||
|
return respond(&mut stream, 405, "Method Not Allowed", "{}");
|
||||||
|
}
|
||||||
|
let authz = header_line(&header_text, "authorization");
|
||||||
|
if !auth_ok(authz.as_deref(), user, token) {
|
||||||
|
return respond_with(
|
||||||
|
&mut stream,
|
||||||
|
401,
|
||||||
|
"Unauthorized",
|
||||||
|
"{\"error\":\"authentication required\"}",
|
||||||
|
Some("WWW-Authenticate: Basic realm=\"echoircd-rpc\""),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let Some(he) = head_end {
|
if let Some(he) = head_end {
|
||||||
|
|
@ -104,7 +125,7 @@ fn handle(
|
||||||
let Some(he) = head_end else {
|
let Some(he) = head_end else {
|
||||||
return respond(&mut stream, 400, "Bad Request", "{}");
|
return respond(&mut stream, 400, "Bad Request", "{}");
|
||||||
};
|
};
|
||||||
let header_text = String::from_utf8_lossy(&buf[..he]).into_owned();
|
// method + auth were already validated on the header block above; extract body
|
||||||
let raw_body = &buf[(he + 4).min(buf.len())..];
|
let raw_body = &buf[(he + 4).min(buf.len())..];
|
||||||
let body = match framing {
|
let body = match framing {
|
||||||
Framing::Length(n) => {
|
Framing::Length(n) => {
|
||||||
|
|
@ -113,28 +134,6 @@ fn handle(
|
||||||
Framing::Chunked => crate::http::dechunk(&String::from_utf8_lossy(raw_body)),
|
Framing::Chunked => crate::http::dechunk(&String::from_utf8_lossy(raw_body)),
|
||||||
};
|
};
|
||||||
|
|
||||||
// request line: only POST is accepted
|
|
||||||
let first = header_text.lines().next().unwrap_or("");
|
|
||||||
if !first
|
|
||||||
.split_whitespace()
|
|
||||||
.next()
|
|
||||||
.is_some_and(|m| m.eq_ignore_ascii_case("POST"))
|
|
||||||
{
|
|
||||||
return respond(&mut stream, 405, "Method Not Allowed", "{}");
|
|
||||||
}
|
|
||||||
|
|
||||||
// authenticate
|
|
||||||
let authz = header_line(&header_text, "authorization");
|
|
||||||
if !auth_ok(authz.as_deref(), user, token) {
|
|
||||||
return respond_with(
|
|
||||||
&mut stream,
|
|
||||||
401,
|
|
||||||
"Unauthorized",
|
|
||||||
"{\"error\":\"authentication required\"}",
|
|
||||||
Some("WWW-Authenticate: Basic realm=\"echoircd-rpc\""),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// parse the JSON-RPC request
|
// parse the JSON-RPC request
|
||||||
let method = super::json::get_str(&body, "method");
|
let method = super::json::get_str(&body, "method");
|
||||||
let id = super::json::get_raw(&body, "id").unwrap_or_else(|| "null".to_string());
|
let id = super::json::get_raw(&body, "id").unwrap_or_else(|| "null".to_string());
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue