Persistencia setters, compat-resolved, journald→CAS, compat-polkit
1//! ente-resolved-compat: shim de `org.freedesktop.resolve1`.
2//!
3//! Bajo el capó usa `tokio::net::lookup_host` (que termina en getaddrinfo
4//! del libc del sistema). No reimplementamos un resolver DNS — delegamos
5//! al stack de resolución del kernel/glibc.
6//!
7//! Métodos cubiertos:
8//! - ResolveHostname (name → addresses)
9//! - ResolveAddress (address → name reverse)
10//! - ResolveRecord (TXT/SRV/etc) — NotSupported (requiere DNS query directa)
11
12use ente_bus::{BusClient, BusRequest, BusResponse};
13use ente_card::Capability;
14use std::net::IpAddr;
15use tokio::signal::unix::{signal, SignalKind};
16use tracing::{info, warn};
17use tracing_subscriber::EnvFilter;
18use zbus::{fdo, interface};
19
20const BUS_NAME: &str = "org.freedesktop.resolve1";
21const OBJ_PATH: &str = "/org/freedesktop/resolve1";
22
23const AF_INET: i32 = 2;
24const AF_INET6: i32 = 10;
25
26#[tokio::main(flavor = "current_thread")]
27async fn main() -> anyhow::Result<()> {
28 init_tracing();
29 info!("ente-resolved-compat: arrancando");
30 announce_to_fractal().await;
31
32 let manager = ResolveManager;
33 let conn_result = zbus::connection::Builder::system()
34 .and_then(|b| b.name(BUS_NAME))
35 .and_then(|b| b.serve_at(OBJ_PATH, manager));
36 match conn_result {
37 Ok(builder) => match builder.build().await {
38 Ok(_conn) => {
39 info!(name = BUS_NAME, "name acquired, sirviendo");
40 wait_for_term().await
41 }
42 Err(e) => { warn!(?e, "build conn falló — modo idle"); wait_for_term().await }
43 },
44 Err(e) => { warn!(?e, "builder D-Bus falló — modo idle"); wait_for_term().await }
45 }
46}
47
48struct ResolveManager;
49
50/// Tipo del wire format de `ResolveHostname`. Por entry: (ifindex, family,
51/// address-as-bytes). systemd-resolved devuelve hasta 4 bytes para AF_INET
52/// y 16 para AF_INET6.
53type HostnameAddress = (i32, i32, Vec<u8>);
54
55#[interface(name = "org.freedesktop.resolve1.Manager")]
56impl ResolveManager {
57 /// Wire signature: `ResolveHostname(in iiusst, out a(iiay)st)` — recibe
58 /// (ifindex, name, family, flags), devuelve (addresses, canonical, flags).
59 async fn resolve_hostname(
60 &self,
61 _ifindex: i32,
62 name: String,
63 family: i32,
64 _flags: u64,
65 ) -> fdo::Result<(Vec<HostnameAddress>, String, u64)> {
66 // tokio::net::lookup_host requiere "host:port"; usamos puerto sentinel.
67 let target = format!("{name}:0");
68 let addrs = match tokio::net::lookup_host(&target).await {
69 Ok(it) => it,
70 Err(e) => return Err(fdo::Error::Failed(format!("lookup_host {name}: {e}"))),
71 };
72 let mut out = Vec::new();
73 for sa in addrs {
74 let ip = sa.ip();
75 let (af, bytes) = match ip {
76 IpAddr::V4(v4) => (AF_INET, v4.octets().to_vec()),
77 IpAddr::V6(v6) => (AF_INET6, v6.octets().to_vec()),
78 };
79 // Filtrado por family si el llamador lo pidió específico.
80 if family != 0 && family != af { continue; }
81 out.push((0i32, af, bytes));
82 }
83 if out.is_empty() {
84 return Err(fdo::Error::Failed(format!("sin resoluciones para {name} (family={family})")));
85 }
86 info!(%name, family, count = out.len(), "ResolveHostname");
87 Ok((out, name, 0))
88 }
89
90 /// Wire signature: `ResolveAddress(in iiayt, out a(is)t)` — (ifindex,
91 /// family, address, flags) → (names, flags).
92 async fn resolve_address(
93 &self,
94 _ifindex: i32,
95 family: i32,
96 address: Vec<u8>,
97 _flags: u64,
98 ) -> fdo::Result<(Vec<(i32, String)>, u64)> {
99 let ip = parse_address(family, &address)
100 .ok_or_else(|| fdo::Error::InvalidArgs(format!("address malformado family={family} bytes={}", address.len())))?;
101 // Reverse lookup vía getnameinfo. Usamos std::net::lookup_addr no existe,
102 // así que invocamos via libc directamente.
103 let name = reverse_lookup(ip)
104 .ok_or_else(|| fdo::Error::Failed(format!("sin reverse para {ip}")))?;
105 info!(%ip, %name, "ResolveAddress");
106 Ok((vec![(0, name)], 0))
107 }
108
109 async fn resolve_record(
110 &self,
111 _ifindex: i32,
112 _name: String,
113 _class: u16,
114 _type_: u16,
115 _flags: u64,
116 ) -> fdo::Result<(Vec<(i32, u16, u16, Vec<u8>)>, u64)> {
117 Err(fdo::Error::NotSupported(
118 "ResolveRecord requiere acceso DNS directo — stub no implementado".into()
119 ))
120 }
121}
122
123fn parse_address(family: i32, bytes: &[u8]) -> Option<IpAddr> {
124 match family {
125 AF_INET if bytes.len() == 4 => {
126 let mut a = [0u8; 4];
127 a.copy_from_slice(bytes);
128 Some(IpAddr::V4(std::net::Ipv4Addr::from(a)))
129 }
130 AF_INET6 if bytes.len() == 16 => {
131 let mut a = [0u8; 16];
132 a.copy_from_slice(bytes);
133 Some(IpAddr::V6(std::net::Ipv6Addr::from(a)))
134 }
135 _ => None,
136 }
137}
138
139/// getnameinfo(3) wrapper. Devuelve None si no resuelve.
140fn reverse_lookup(ip: IpAddr) -> Option<String> {
141 use std::os::raw::c_char;
142 let mut buf = [0i8; 256];
143 let r = match ip {
144 IpAddr::V4(v4) => unsafe {
145 let octets = v4.octets();
146 let mut sin = std::mem::zeroed::<libc::sockaddr_in>();
147 sin.sin_family = libc::AF_INET as u16;
148 sin.sin_addr = libc::in_addr {
149 s_addr: u32::from_ne_bytes(octets),
150 };
151 libc::getnameinfo(
152 &sin as *const _ as *const libc::sockaddr,
153 std::mem::size_of::<libc::sockaddr_in>() as u32,
154 buf.as_mut_ptr() as *mut c_char, buf.len() as u32,
155 std::ptr::null_mut(), 0,
156 libc::NI_NAMEREQD,
157 )
158 },
159 IpAddr::V6(v6) => unsafe {
160 let octets = v6.octets();
161 let mut sin6 = std::mem::zeroed::<libc::sockaddr_in6>();
162 sin6.sin6_family = libc::AF_INET6 as u16;
163 sin6.sin6_addr.s6_addr.copy_from_slice(&octets);
164 libc::getnameinfo(
165 &sin6 as *const _ as *const libc::sockaddr,
166 std::mem::size_of::<libc::sockaddr_in6>() as u32,
167 buf.as_mut_ptr() as *mut c_char, buf.len() as u32,
168 std::ptr::null_mut(), 0,
169 libc::NI_NAMEREQD,
170 )
171 },
172 };
173 if r != 0 { return None; }
174 let cs = unsafe { std::ffi::CStr::from_ptr(buf.as_ptr()) };
175 cs.to_str().ok().map(String::from)
176}
177
178extern crate libc;
179
180async fn announce_to_fractal() {
181 if let Ok(mut client) = BusClient::from_env().await {
182 let req = BusRequest::Announce {
183 capabilities: vec![Capability::Endpoint {
184 interface: ente_card::InterfaceId([0xa3; 16]),
185 version: 1,
186 }],
187 };
188 match client.call(req).await {
189 Ok(BusResponse::Ok) => info!("Announce → bus interno OK"),
190 Ok(other) => warn!(?other, "Announce respuesta inesperada"),
191 Err(e) => warn!(?e, "Announce falló"),
192 }
193 }
194}
195
196async fn wait_for_term() -> anyhow::Result<()> {
197 let mut term = signal(SignalKind::terminate())?;
198 let mut int_ = signal(SignalKind::interrupt())?;
199 tokio::select! {
200 _ = term.recv() => info!("SIGTERM"),
201 _ = int_.recv() => info!("SIGINT"),
202 }
203 Ok(())
204}
205
206fn init_tracing() {
207 let filter = EnvFilter::try_from_default_env()
208 .unwrap_or_else(|_| EnvFilter::new("ente_resolved_compat=info"));
209 tracing_subscriber::fmt().with_env_filter(filter).with_target(true).init();
210}