address review: bind the control port before writing the token file

This commit is contained in:
Dylan M. Taylor committed 2026-11-11 14:48:24 +00:00
1 parent c59b50da54
commit d67874e90b
2 files changed
+34 -13

No files matched your search

+19 -13
View File
@@ -117,6 +117,24 @@ fn authenticate(stream: &mut TcpStream, token: &str) -> std::io::Result<()> {
Err(std::io::Error::new(std::io::ErrorKind::PermissionDenied, "control token refused"))
}
/// Bind first: a second launch on a taken port must not overwrite the running instance's token file.
fn bind_then_write_token(port: u16, path: &Path, token: &str) -> Option<TcpListener> {
let listener = match TcpListener::bind(("127.0.0.1", port)) {
Ok(l) => l,
Err(e) => {
log::error!("control server: can't bind 127.0.0.1:{port}: {e} (another app may own it; pick another port)");
eprintln!("FigCraft control server failed to bind port {port}: {e}");
return None;
}
};
if let Err(e) = write_token(path, token) {
log::error!("control server disabled: can't write the token to {}: {e}", path.display());
eprintln!("FigCraft control server disabled: can't write the token to {}: {e}", path.display());
return None;
}
Some(listener)
}
pub fn start(port: u16, queue: Queue, ctx: CtxSlot) {
// Fail closed: without a token file the channel stays off rather than open to any local client.
let token = match random_token() {
@@ -132,19 +150,7 @@ pub fn start(port: u16, queue: Queue, ctx: CtxSlot) {
eprintln!("FigCraft control server disabled: no config folder for the token");
return;
};
if let Err(e) = write_token(&path, &token) {
log::error!("control server disabled: can't write the token to {}: {e}", path.display());
eprintln!("FigCraft control server disabled: can't write the token to {}: {e}", path.display());
return;
}
let listener = match TcpListener::bind(("127.0.0.1", port)) {
Ok(l) => l,
Err(e) => {
log::error!("control server: can't bind 127.0.0.1:{port}: {e} (another app may own it; pick another port)");
eprintln!("FigCraft control server failed to bind port {port}: {e}");
return;
}
};
let Some(listener) = bind_then_write_token(port, &path, &token) else { return };
eprintln!("FigCraft control server listening on 127.0.0.1:{port}; token in {}", path.display());
let token: Arc<str> = Arc::from(token);
let spawned = std::thread::Builder::new().name("figcraft-control".into()).spawn(move || {
+15
View File
@@ -381,3 +381,18 @@ fn tokens_are_fresh_hex_and_the_file_is_user_only() {
}
std::fs::remove_dir_all(&dir).unwrap();
}
#[test]
fn a_failed_bind_leaves_the_existing_token_file_alone() {
let taken = TcpListener::bind(("127.0.0.1", 0)).unwrap();
let port = taken.local_addr().unwrap().port();
let path = std::env::temp_dir().join(format!("figcraft-bind-first-{port}.token"));
std::fs::write(&path, "old").unwrap();
assert!(bind_then_write_token(port, &path, "new").is_none());
assert_eq!(std::fs::read_to_string(&path).unwrap(), "old");
drop(taken);
let listener = bind_then_write_token(port, &path, "new").unwrap();
assert_eq!(std::fs::read_to_string(&path).unwrap(), "new");
drop(listener);
std::fs::remove_file(&path).unwrap();
}