fix: scope server pins by port (fumi-core rev 1468f3c)

This commit is contained in:
randogoth 2026-09-30 08:52:57 +03:00
parent 996e879509
commit 7d2a147fec
16 changed files with 109 additions and 79 deletions

2
native/Cargo.lock generated
View file

@ -271,7 +271,7 @@ checksum = "aedcfb3409746eddb02b9e19ebda1c3394f759a152e48ee875a0844d1b955484"
[[package]]
name = "fumi-core"
version = "0.1.0"
source = "git+ssh://git@code.randogoth.com:2222/randogoth/fumi.git?rev=2d65d66#2d65d66012fa40121e4dc3d8d15db9ffc486569a"
source = "git+ssh://git@code.randogoth.com:2222/randogoth/fumi.git?rev=1468f3c#1468f3cbc0a89a0cb073f46a985911c0327885d1"
dependencies = [
"chacha20poly1305",
"data-encoding",

View file

@ -7,7 +7,7 @@ edition = "2021"
crate-type = ["lib", "cdylib"]
[dependencies]
fumi-core = { git = "ssh://git@code.randogoth.com:2222/randogoth/fumi.git", rev = "2d65d66" }
fumi-core = { git = "ssh://git@code.randogoth.com:2222/randogoth/fumi.git", rev = "1468f3c" }
rand_core = { version = "0.6", features = ["getrandom"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"

View file

@ -126,22 +126,29 @@ struct ErrorJson {
/// The raw byte behind an UNKNOWN_STATUS (code 11).
#[serde(skip_serializing_if = "Option::is_none")]
status: Option<u8>,
#[serde(skip_serializing_if = "Option::is_none")]
port: Option<u16>,
}
impl ErrorJson {
fn of(error: &Error) -> ErrorJson {
let message = error.to_string();
let (host, pinned, presented, address, known, offered, status) = match error {
let (host, pinned, presented, address, known, offered, status, port) = match error {
Error::UnknownStatus(status, _) => {
(None, None, None, None, None, None, Some(*status))
(None, None, None, None, None, None, Some(*status), None)
}
Error::NotPinned { host, port } => {
(Some(host.clone()), None, None, None, None, None, None, Some(*port))
}
Error::Unreachable { host, .. } => {
(Some(host.clone()), None, None, None, None, None, None, None)
}
Error::NotPinned { host } => (Some(host.clone()), None, None, None, None, None, None),
Error::Unreachable { host, .. } => (Some(host.clone()), None, None, None, None, None, None),
Error::HandshakeRefused { host, .. } => {
(Some(host.clone()), None, None, None, None, None, None)
(Some(host.clone()), None, None, None, None, None, None, None)
}
Error::PinMismatch {
host,
port,
pinned,
presented,
} => (
@ -152,6 +159,7 @@ impl ErrorJson {
None,
None,
None,
Some(*port),
),
Error::KeyChanged {
address,
@ -165,8 +173,9 @@ impl ErrorJson {
Some(b32(known)),
Some(b32(offered)),
None,
None,
),
_ => (None, None, None, None, None, None, None),
_ => (None, None, None, None, None, None, None, None),
};
ErrorJson {
code: code_of(error),
@ -178,6 +187,7 @@ impl ErrorJson {
known,
offered,
status,
port,
}
}
}
@ -383,23 +393,24 @@ pub extern "C" fn smol_pin_server(
store: *mut Store,
host: *const c_char,
key_b32: *const c_char,
port: u16,
) -> i32 {
status(|| {
let store = unsafe { store.as_ref().ok_or_else(|| Error::Other("store handle is null".into()))? };
let key: [u8; KEY_LEN] = unb32(text(key_b32)?)?
.try_into()
.map_err(|_| Error::Other("server key must decode to 32 bytes".into()))?;
store.pin_server(text(host)?, &key)
store.pin_server(text(host)?, port, &key)
})
}
/// The pinned key for a host, base32. Empty string: none pinned. Null: error.
#[no_mangle]
pub extern "C" fn smol_server_pin(store: *mut Store, host: *const c_char) -> *mut c_char {
pub extern "C" fn smol_server_pin(store: *mut Store, host: *const c_char, port: u16) -> *mut c_char {
match guarded(|| {
let store = unsafe { store.as_ref().ok_or_else(|| Error::Other("store handle is null".into()))? };
Ok(store
.server_pin(text(host)?)?
.server_pin(text(host)?, port)?
.map(|key| b32(&key))
.unwrap_or_default())
}) {
@ -426,12 +437,13 @@ pub extern "C" fn smol_contact_save(
})
}
/// Removes a pin: the next session against that host is trust on first use.
/// Removes a pin: the next session against that host and port is trust on
/// first use.
#[no_mangle]
pub extern "C" fn smol_unpin_server(store: *mut Store, host: *const c_char) -> i32 {
pub extern "C" fn smol_unpin_server(store: *mut Store, host: *const c_char, port: u16) -> i32 {
status(|| {
let store = unsafe { store.as_ref().ok_or_else(|| Error::Other("store handle is null".into()))? };
store.unpin_server(text(host)?)
store.unpin_server(text(host)?, port)
})
}
@ -1178,6 +1190,7 @@ mod tests {
let json: serde_json::Value = serde_json::from_str(&slot).unwrap();
assert_eq!(json["code"], NOT_PINNED);
assert_eq!(json["host"], "127.0.0.1");
assert_eq!(json["port"], 19619);
// Strings returned on success must be freed without double-free.
assert_eq!(take(smol_store_account(store)), "");
@ -1193,7 +1206,8 @@ mod tests {
fn mail_and_contacts_render_as_json() {
let store = smol_store_memory();
let s = unsafe { &*store };
s.pin_server("example.org", &[1u8; KEY_LEN]).unwrap();
s.pin_server("example.org", fumi_core::address::DEFAULT_PORT, &[1u8; KEY_LEN])
.unwrap();
s.save_contact("alice@example.org", &[2u8; KEY_LEN], true).unwrap();
s.save_history("alice@example.org", &[9u8; KEY_LEN], 500).unwrap();
s.store_inbox(&[3u8; ID_LEN], b"sealed", 7, false, true).unwrap();

View file

@ -86,8 +86,8 @@ mod tests {
let alice_addr =
Address::parse(&format!("a{run}@127.0.0.1:19619")).unwrap();
let bob_addr = Address::parse(&format!("b{run}@127.0.0.1:19619")).unwrap();
alice_store.pin_server("127.0.0.1", &server).unwrap();
bob_store.pin_server("127.0.0.1", &server).unwrap();
alice_store.pin_server("127.0.0.1", 19619, &server).unwrap();
bob_store.pin_server("127.0.0.1", 19619, &server).unwrap();
register(&alice_store, &alice_addr, &alice, None, 5).unwrap();
register(&bob_store, &bob_addr, &bob, None, 5).unwrap();
@ -168,8 +168,8 @@ mod tests {
let run = fumi_core::crypto::b32(&suffix);
let alice_addr = Address::parse(&format!("a{run}@127.0.0.1:19619")).unwrap();
let bob_addr = Address::parse(&format!("b{run}@127.0.0.1:19619")).unwrap();
alice_store.pin_server("127.0.0.1", &server).unwrap();
bob_store.pin_server("127.0.0.1", &server).unwrap();
alice_store.pin_server("127.0.0.1", 19619, &server).unwrap();
bob_store.pin_server("127.0.0.1", 19619, &server).unwrap();
register(&alice_store, &alice_addr, &alice, None, 5).unwrap();
register(&bob_store, &bob_addr, &bob, None, 5).unwrap();
@ -272,7 +272,7 @@ mod tests {
fumi_core::address::Address::parse(&format!("u{run}@{host}:19619"))
.unwrap()
.with_dial("127.0.0.1");
store.pin_server(&host, &server).unwrap();
store.pin_server(&host, 19619, &server).unwrap();
register(&store, &addr, &account, None, 5).unwrap();
assert_eq!(store.account().unwrap().unwrap().short(),
format!("u{run}@{host}:19619"));
@ -296,7 +296,10 @@ mod tests {
let unpinned = Store::open_in_memory().unwrap();
match connect(&unpinned, &addr, true, 5) {
Err(Error::NotPinned { host }) => assert_eq!(host, "127.0.0.1"),
Err(Error::NotPinned { host, port }) => {
assert_eq!(host, "127.0.0.1");
assert_eq!(port, 19619);
}
Err(err) => panic!("expected NotPinned, got {err}"),
Ok(_) => panic!("expected NotPinned, connected"),
}
@ -304,7 +307,7 @@ mod tests {
let mut wrong = [0u8; KEY_LEN];
OsRng.fill_bytes(&mut wrong);
let mismatched = Store::open_in_memory().unwrap();
mismatched.pin_server("127.0.0.1", &wrong).unwrap();
mismatched.pin_server("127.0.0.1", 19619, &wrong).unwrap();
match connect(&mismatched, &addr, true, 5) {
Err(Error::PinMismatch { .. }) => {}
Err(err) => panic!("expected PinMismatch, got {err}"),
@ -348,8 +351,8 @@ mod tests {
let run = fumi_core::crypto::b32(&suffix);
let carol_addr = Address::parse(&format!("c{run}@127.0.0.1:19619")).unwrap();
let alice_addr = Address::parse(&format!("d{run}@127.0.0.1:19619")).unwrap();
carol_store.pin_server("127.0.0.1", &server).unwrap();
alice_store.pin_server("127.0.0.1", &server).unwrap();
carol_store.pin_server("127.0.0.1", 19619, &server).unwrap();
alice_store.pin_server("127.0.0.1", 19619, &server).unwrap();
register(&carol_store, &carol_addr, &carol, None, 5).unwrap();
register(&alice_store, &alice_addr, &alice, None, 5).unwrap();