This commit is contained in:
Mateusz Gruszczyński committed 2026-11-08 08:43:14 +01:00
1 parent 19dfc51360
commit b6e8ddb9c7
31 files changed
+1080 -32

No files matched your search

+84 -21
View File
@@ -62,16 +62,80 @@ fn safe_host(h: &str) -> Option<&str> {
.then_some(h)
}
/// Every attempt is logged and shown in the panel (Live), including the unauthenticated
/// first request iOS sends and failures - this is what makes "my phone still asks for the
/// server" debuggable.
pub async fn handler(
State(app): State<Arc<App>>,
ConnectInfo(peer): ConnectInfo<SocketAddr>,
method: Method,
uri: axum::http::Uri,
headers: HeaderMap,
body: Body,
) -> Response {
let s = app.settings();
let started = std::time::Instant::now();
let ip = client_ip(peer, &headers, &s).to_string();
let hdr = |n: &str| {
headers
.get(n)
.and_then(|v| v.to_str().ok())
.unwrap_or("")
.chars()
.filter(|c| !c.is_control())
.take(120)
.collect::<String>()
};
let host = if hdr("x-forwarded-host").is_empty() {
hdr("host")
} else {
hdr("x-forwarded-host")
};
let ua = hdr("user-agent");
let user = basic_auth(&headers, &s)
.map(|(l, _)| l)
.unwrap_or_else(|| "-".into());
let size = hdr("content-length").parse::<u64>().unwrap_or(0);
let resp = inner(app.clone(), peer, method.clone(), headers, body).await;
let mut resp = resp;
resp.headers_mut()
.insert("X-ActivePush", HeaderValue::from_static("autodiscover"));
let code = resp.status().as_u16();
let status = match code {
200 => "ok",
401 => "auth required",
429 => "locked out",
503 => "backend unavailable",
_ => "error",
};
tracing::info!(target: "active_push::autodiscover", ip = %ip, host = %host, path = %uri.path(), method = %method, http = code, user = %user, ua = %ua, "autodiscover {status}");
app.metrics.record(Event {
ts: now(),
user,
device_id: String::new(),
device_type: "Autodiscover".into(),
command: "Autodiscover".into(),
ip,
http: code,
status: format!("{status} ({host})"),
ms: started.elapsed().as_millis() as u64,
bytes_in: size,
bytes_out: 0,
items_out: 0,
items_in: 0,
});
resp
}
async fn inner(
app: Arc<App>,
peer: SocketAddr,
method: Method,
headers: HeaderMap,
body: Body,
) -> Response {
let s = app.settings();
let ip = client_ip(peer, &headers, &s).to_string();
let started = std::time::Instant::now();
app.metrics
.c
.autodiscover
@@ -101,11 +165,22 @@ pub async fn handler(
Err(_) => return xml_response(StatusCode::OK, error_xml(600, "Invalid Request")),
}
};
if schema != MOBILESYNC {
tracing::info!(ip = %ip, schema = %schema, "autodiscover: unsupported response schema");
if !schema.is_empty() && schema != MOBILESYNC {
tracing::info!(ip = %ip, schema = %schema, "autodiscover: unsupported response schema (only MobileSync is served)");
return xml_response(StatusCode::OK, error_xml(600, "Invalid Request"));
}
// only the configured domains (when a list is set)
if !s.autodiscover.domains.is_empty() {
if let Some((_, dom)) = email.rsplit_once('@') {
let dom = crate::autodiag::normalize_domain(dom);
if !s.autodiscover.serves(&dom) {
tracing::info!(ip = %ip, domain = %dom, "autodiscover: domain not in the configured list");
return xml_response(StatusCode::OK, error_xml(600, "Invalid Request"));
}
}
}
// authentication
let creds = basic_auth(&headers, &s);
let login = match (&creds, s.autodiscover.require_auth) {
@@ -158,8 +233,12 @@ pub async fn handler(
if email.is_empty() || !email.contains('@') {
email = crate::backend::primary_email(&s, &login);
}
let url = if !s.autodiscover.server_url.is_empty() {
s.autodiscover.server_url.clone()
let configured = email
.rsplit_once('@')
.map(|(_, d)| s.autodiscover.url_for(d))
.unwrap_or_else(|| s.autodiscover.server_url.trim().to_string());
let url = if !configured.is_empty() {
configured
} else {
let host = headers
.get("x-forwarded-host")
@@ -195,21 +274,5 @@ pub async fn handler(
xml_escape(&url),
xml_escape(&url)
);
tracing::info!(user = %login, ip = %ip, "autodiscover answered");
app.metrics.record(Event {
ts: now(),
user: login,
device_id: String::new(),
device_type: "Autodiscover".into(),
command: "Autodiscover".into(),
ip,
http: 200,
status: "ok".into(),
ms: started.elapsed().as_millis() as u64,
bytes_in: body.len() as u64,
bytes_out: xml.len() as u64,
items_out: 0,
items_in: 0,
});
xml_response(StatusCode::OK, xml)
}