Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ on:
- main
pull_request:

permissions:
contents: read

jobs:
rust:
runs-on: ubuntu-latest
Expand Down
2 changes: 2 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ RUN dnf -y install \
git \
glibc-devel \
make \
openssl-devel \
pkgconf-pkg-config \
tar \
xz \
Expand All @@ -31,6 +32,7 @@ COPY rust-toolchain.toml Cargo.toml Cargo.lock ./
COPY crates ./crates
COPY assets ./assets
COPY tools ./tools
COPY web-terminal ./web-terminal

RUN cargo build --workspace --locked --release -p oxidebbs-server

Expand Down
43 changes: 31 additions & 12 deletions crates/oxidebbs-door/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1391,11 +1391,20 @@ mod tests {
use std::io::{Read, Write};
use std::net::{Shutdown, TcpListener};
use std::thread::JoinHandle;
use std::time::{SystemTime, UNIX_EPOCH};

fn temp_path(name: &str) -> PathBuf {
std::env::temp_dir().join(format!("oxidebbs-door-{name}-{}", std::process::id()))
}

fn remote_provider_secret() -> String {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("time")
.as_nanos()
.to_string()
}

fn caller() -> DoorCaller {
DoorCaller {
alias: "Alice".to_string(),
Expand Down Expand Up @@ -2026,9 +2035,10 @@ command = "LORD.EXE"

#[test]
fn doorparty_provider_dry_run_validates_config_without_network() {
let provider_password = remote_provider_secret();
let provider = DoorPartyProvider::new(DoorPartyConfig::new(
"oxide-account",
"doorparty-password",
provider_password,
"telnet://doorparty.example:23",
));

Expand All @@ -2047,8 +2057,11 @@ command = "LORD.EXE"
fn remote_provider_validation_rejects_missing_required_fields() {
let missing_bbslink_auth =
BbsLinkProvider::new(BbsLinkConfig::new("oxide-system", "", "bbslink.example:23"));
let missing_doorparty_endpoint =
DoorPartyProvider::new(DoorPartyConfig::new("oxide-account", "secret", " "));
let missing_doorparty_endpoint = DoorPartyProvider::new(DoorPartyConfig::new(
"oxide-account",
remote_provider_secret(),
" ",
));

let bbslink_error = missing_bbslink_auth
.validate_config()
Expand All @@ -2069,9 +2082,10 @@ command = "LORD.EXE"

#[test]
fn remote_provider_secrets_are_redacted_by_default() {
let provider_password = remote_provider_secret();
let provider = DoorPartyProvider::new(DoorPartyConfig::new(
"oxide-account",
"doorparty-password",
provider_password.clone(),
"doorparty.example:23",
));

Expand All @@ -2080,13 +2094,13 @@ command = "LORD.EXE"

assert_eq!(
provider.config().password.expose_secret(),
"doorparty-password"
provider_password.as_str()
);
assert_eq!(
provider.config().password.to_string(),
REDACTED_PROVIDER_SECRET
);
assert!(!config_debug.contains("doorparty-password"));
assert!(!config_debug.contains(&provider_password));
assert!(config_debug.contains(REDACTED_PROVIDER_SECRET));
assert_eq!(redacted.provider_key, DOORPARTY_PROVIDER_KEY);
assert_eq!(redacted.secret, REDACTED_PROVIDER_SECRET);
Expand Down Expand Up @@ -2262,9 +2276,10 @@ command = "LORD.EXE"

#[test]
fn doorparty_provider_launch_session_sends_auth_and_user() {
let provider_password = remote_provider_secret();
let provider = DoorPartyProvider::new(DoorPartyConfig::new(
"oxide-account",
"doorparty-password",
provider_password.clone(),
"doorparty.example:23",
));

Expand All @@ -2278,7 +2293,7 @@ command = "LORD.EXE"
assert!(!result.timed_out);

let sent = String::from_utf8_lossy(io.remote_received());
assert!(sent.contains("ACCT oxide-account PASS doorparty-password"));
assert!(sent.contains(&format!("ACCT oxide-account PASS {provider_password}")));
assert!(sent.contains("USER Alice SEC 50"));
}

Expand All @@ -2304,8 +2319,11 @@ command = "LORD.EXE"

#[test]
fn doorparty_provider_launch_session_rejects_invalid_config() {
let provider =
DoorPartyProvider::new(DoorPartyConfig::new("", "password", "doorparty.example:23"));
let provider = DoorPartyProvider::new(DoorPartyConfig::new(
"",
remote_provider_secret(),
"doorparty.example:23",
));

let mut io = FakeRemoteSessionIo::new(b"", b"");
let result = provider.launch_session(&caller(), &mut io);
Expand Down Expand Up @@ -2338,9 +2356,10 @@ command = "LORD.EXE"
#[test]
fn doorparty_provider_live_connector_uses_local_tcp_server() {
let (endpoint, server) = spawn_fake_provider_server(b"OK\r\nWelcome DoorParty\r\n");
let provider_password = remote_provider_secret();
let provider = DoorPartyProvider::new(DoorPartyConfig::new(
"oxide-account",
"doorparty-password",
provider_password.clone(),
endpoint,
));

Expand All @@ -2352,7 +2371,7 @@ command = "LORD.EXE"

assert_eq!(result.run_result.exit_code, Some(0));
assert!(!result.run_result.timed_out);
assert!(received.contains("ACCT oxide-account PASS doorparty-password"));
assert!(received.contains(&format!("ACCT oxide-account PASS {provider_password}")));
assert!(received.contains("USER Alice SEC 50"));
assert_eq!(result.caller_output, b"OK\r\nWelcome DoorParty\r\n");
}
Expand Down
9 changes: 7 additions & 2 deletions crates/oxidebbs-ftn/src/scanner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -788,6 +788,10 @@ mod tests {
}
}

fn ftn_password() -> String {
format!("{:08}", std::process::id() % 100_000_000)
}

fn link() -> NetworkLinkRecord {
NetworkLinkRecord {
id: LINK_ID.to_string(),
Expand All @@ -796,7 +800,7 @@ mod tests {
address: "1:105/1".to_string(),
host: "hub.example".to_string(),
binkp_port: 24554,
password: "SECRET".to_string(),
password: ftn_password(),
poll_schedule_minutes: 60,
compression: "none".to_string(),
transport_security: "plaintext_legacy".to_string(),
Expand Down Expand Up @@ -847,8 +851,9 @@ mod tests {
let body = format!(
"AREA:OXIDE.GENERAL\r\x01MSGID: 1:105/1 {msgid_suffix}\rInbound body\rSEEN-BY: 105/1\rPATH: 105/1\r"
);
let password = ftn_password();
let packet = FtnPacket {
header: inbound_header("SECRET"),
header: inbound_header(&password),
messages: vec![PacketMessage {
to_user: "All".to_string(),
from_user: "Hub".to_string(),
Expand Down
32 changes: 25 additions & 7 deletions crates/oxidebbs-ftn/src/tosser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1214,6 +1214,7 @@ mod tests {

fn test_db() -> OxideDb {
let db = OxideDb::open_memory().expect("open db");
let password = ftn_password();
insert_message_area(
db.db(),
&MessageAreaRecord {
Expand All @@ -1231,7 +1232,7 @@ mod tests {
)
.expect("insert area");
insert_network_profile(db.db(), &profile()).expect("insert profile");
insert_network_link(db.db(), &link("SECRET")).expect("insert link");
insert_network_link(db.db(), &link(&password)).expect("insert link");
insert_network_area(
db.db(),
&NetworkAreaRecord {
Expand Down Expand Up @@ -1266,6 +1267,18 @@ mod tests {
}
}

fn ftn_password() -> String {
format!("{:08}", std::process::id() % 100_000_000)
}

fn mismatched_ftn_password() -> String {
let mut bytes = ftn_password().into_bytes();
if let Some(first) = bytes.first_mut() {
*first = if *first == b'9' { b'8' } else { b'9' };
}
String::from_utf8(bytes).expect("packet password text")
}

fn link(password: &str) -> NetworkLinkRecord {
NetworkLinkRecord {
id: LINK_ID.to_string(),
Expand Down Expand Up @@ -1355,10 +1368,11 @@ mod tests {
fn tosses_known_echomail_packet_into_local_area() {
let db = test_db();
let root = temp_root("good");
let password = ftn_password();
let _packet = packet_path(
&root,
"00000001.pkt",
"SECRET",
&password,
b"AREA:OXIDE.GENERAL\r\x01MSGID: 1:105/1 abc\rHello from FTN\rSEEN-BY: 105/1\rPATH: 105/1\r",
);
let tosser = Tosser::new(
Expand Down Expand Up @@ -1390,10 +1404,11 @@ mod tests {
fn wrong_password_quarantines_packet_without_importing_messages() {
let db = test_db();
let root = temp_root("bad-password");
let password = mismatched_ftn_password();
let _packet = packet_path(
&root,
"00000002.pkt",
"WRONG",
&password,
b"AREA:OXIDE.GENERAL\r\x01MSGID: 1:105/1 abc\rHello\r",
);
let tosser = Tosser::new(
Expand Down Expand Up @@ -1421,7 +1436,8 @@ mod tests {
let db = test_db();
let root = temp_root("duplicate");
let body = b"AREA:OXIDE.GENERAL\r\x01MSGID: 1:105/1 duplicate\rHello\r";
let _first = packet_path(&root, "00000003.pkt", "SECRET", body);
let password = ftn_password();
let _first = packet_path(&root, "00000003.pkt", &password, body);
let tosser = Tosser::new(
db.db(),
profile(),
Expand All @@ -1430,7 +1446,7 @@ mod tests {
let first = tosser.toss().expect("first toss");
assert_eq!(first.messages_imported, 1);

let _second = packet_path(&root, "00000004.pkt", "SECRET", body);
let _second = packet_path(&root, "00000004.pkt", &password, body);
let second = tosser.toss().expect("second toss");

assert_eq!(second.messages_imported, 0);
Expand All @@ -1455,7 +1471,8 @@ mod tests {
})
.collect();

let _packet = packet_path_with_messages(&root, "large.pkt", "SECRET", bodies);
let password = ftn_password();
let _packet = packet_path_with_messages(&root, "large.pkt", &password, bodies);
let tosser = Tosser::new(
db.db(),
profile(),
Expand Down Expand Up @@ -1488,7 +1505,8 @@ mod tests {
"AREA:OXIDE.GENERAL\r\x01MSGID: 1:105/1 unique{}\rBody {}\rSEEN-BY: 105/1\rPATH: 105/1\r",
i, i
);
let _packet = packet_path(&root, &format!("{:08}.pkt", i), "SECRET", body.as_bytes());
let password = ftn_password();
let _packet = packet_path(&root, &format!("{:08}.pkt", i), &password, body.as_bytes());
}

let tosser = Tosser::new(
Expand Down
Loading