Add explicit environment propagation
This commit is contained in:
+144
-8
@@ -7,9 +7,9 @@ use dosh::auth::{
|
||||
use dosh::config::{ServerConfig, load_server_config};
|
||||
use dosh::crypto;
|
||||
use dosh::native::{
|
||||
ForwardingKind, ForwardingRequest, NativeAuthOk, NativeServerHello, derive_native_session_key,
|
||||
generate_native_ephemeral, host_public_key, host_public_key_line, load_or_create_host_key,
|
||||
sign_server_hello, verify_native_user_auth_from_config,
|
||||
EnvVar, ForwardingKind, ForwardingRequest, NativeAuthOk, NativeServerHello,
|
||||
derive_native_session_key, generate_native_ephemeral, host_public_key, host_public_key_line,
|
||||
load_or_create_host_key, sign_server_hello, verify_native_user_auth_from_config,
|
||||
};
|
||||
use dosh::protocol::{
|
||||
self, AttachOk, AttachReject, BootstrapAttachRequest, CLIENT_TO_SERVER, Frame, Input,
|
||||
@@ -115,7 +115,7 @@ async fn serve(config_path: Option<std::path::PathBuf>) -> Result<()> {
|
||||
{
|
||||
let mut locked = state.lock().expect("server state poisoned");
|
||||
for session in config.prewarm_sessions.clone() {
|
||||
locked.ensure_session(&session, 80, 24, "read-write")?;
|
||||
locked.ensure_session(&session, 80, 24, "read-write", &[])?;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -225,7 +225,15 @@ impl ServerState {
|
||||
}
|
||||
}
|
||||
|
||||
fn ensure_session(&mut self, name: &str, cols: u16, rows: u16, mode: &str) -> Result<()> {
|
||||
fn ensure_session(
|
||||
&mut self,
|
||||
name: &str,
|
||||
cols: u16,
|
||||
rows: u16,
|
||||
mode: &str,
|
||||
requested_env: &[EnvVar],
|
||||
) -> Result<()> {
|
||||
let env = accepted_env(&self.config, requested_env)?;
|
||||
if let Some(session) = self.sessions.get_mut(name) {
|
||||
if session.pty.is_none() && mode_uses_pty(mode) {
|
||||
session.pty = Some(spawn_pty_session(
|
||||
@@ -233,6 +241,7 @@ impl ServerState {
|
||||
&self.config.shell,
|
||||
cols.max(1),
|
||||
rows.max(1),
|
||||
&env,
|
||||
self.pty_tx.clone(),
|
||||
)?);
|
||||
}
|
||||
@@ -244,6 +253,7 @@ impl ServerState {
|
||||
&self.config.shell,
|
||||
cols.max(1),
|
||||
rows.max(1),
|
||||
&env,
|
||||
self.pty_tx.clone(),
|
||||
)?)
|
||||
} else {
|
||||
@@ -271,6 +281,69 @@ fn mode_allows_terminal_updates(mode: &str) -> bool {
|
||||
mode != "view-only" && mode != "forward-only"
|
||||
}
|
||||
|
||||
fn accepted_env(config: &ServerConfig, requested: &[EnvVar]) -> Result<Vec<(String, String)>> {
|
||||
let mut env = Vec::new();
|
||||
for entry in requested {
|
||||
anyhow::ensure!(
|
||||
valid_env_name(&entry.name),
|
||||
"invalid environment variable name {:?}",
|
||||
entry.name
|
||||
);
|
||||
anyhow::ensure!(
|
||||
!entry.value.as_bytes().contains(&0),
|
||||
"environment variable {:?} contains NUL",
|
||||
entry.name
|
||||
);
|
||||
if config
|
||||
.accept_env
|
||||
.iter()
|
||||
.any(|pattern| glob_matches(pattern, &entry.name))
|
||||
{
|
||||
env.push((entry.name.clone(), entry.value.clone()));
|
||||
}
|
||||
}
|
||||
Ok(env)
|
||||
}
|
||||
|
||||
fn valid_env_name(name: &str) -> bool {
|
||||
let mut chars = name.chars();
|
||||
let Some(first) = chars.next() else {
|
||||
return false;
|
||||
};
|
||||
if !(first == '_' || first.is_ascii_alphabetic()) {
|
||||
return false;
|
||||
}
|
||||
chars.all(|ch| ch == '_' || ch.is_ascii_alphanumeric())
|
||||
}
|
||||
|
||||
fn glob_matches(pattern: &str, value: &str) -> bool {
|
||||
let pattern = pattern.as_bytes();
|
||||
let value = value.as_bytes();
|
||||
let (mut p, mut v) = (0, 0);
|
||||
let mut star = None;
|
||||
let mut star_value = 0;
|
||||
while v < value.len() {
|
||||
if p < pattern.len() && (pattern[p] == b'?' || pattern[p] == value[v]) {
|
||||
p += 1;
|
||||
v += 1;
|
||||
} else if p < pattern.len() && pattern[p] == b'*' {
|
||||
star = Some(p);
|
||||
p += 1;
|
||||
star_value = v;
|
||||
} else if let Some(star_pos) = star {
|
||||
p = star_pos + 1;
|
||||
star_value += 1;
|
||||
v = star_value;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
while p < pattern.len() && pattern[p] == b'*' {
|
||||
p += 1;
|
||||
}
|
||||
p == pattern.len()
|
||||
}
|
||||
|
||||
async fn handle_packet(
|
||||
state: &Arc<Mutex<ServerState>>,
|
||||
socket: &Arc<UdpSocket>,
|
||||
@@ -495,7 +568,13 @@ async fn handle_native_user_auth(
|
||||
if !locked.sessions.contains_key(&session_name) && !locked.config.create_on_attach {
|
||||
return Err(anyhow!("session does not exist"));
|
||||
}
|
||||
locked.ensure_session(&session_name, cols, rows, &mode)?;
|
||||
locked.ensure_session(
|
||||
&session_name,
|
||||
cols,
|
||||
rows,
|
||||
&mode,
|
||||
&pending.client.requested_env,
|
||||
)?;
|
||||
let session_key = pending.session_key;
|
||||
let key_id = protocol::session_key_id(&session_key);
|
||||
let attach_ticket_psk = crypto::random_32();
|
||||
@@ -636,6 +715,7 @@ async fn handle_bootstrap_attach(
|
||||
req.cols,
|
||||
req.rows,
|
||||
&req.bootstrap.mode,
|
||||
&req.requested_env,
|
||||
)?;
|
||||
let session = locked
|
||||
.sessions
|
||||
@@ -742,7 +822,13 @@ async fn handle_ticket_attach(
|
||||
if !locked.sessions.contains_key(&req.session) && !locked.config.create_on_attach {
|
||||
return send_reject(socket, peer, "session does not exist").await;
|
||||
}
|
||||
locked.ensure_session(&req.session, req.cols, req.rows, &req.mode)?;
|
||||
locked.ensure_session(
|
||||
&req.session,
|
||||
req.cols,
|
||||
req.rows,
|
||||
&req.mode,
|
||||
&req.requested_env,
|
||||
)?;
|
||||
let session = locked
|
||||
.sessions
|
||||
.get_mut(&req.session)
|
||||
@@ -1709,9 +1795,59 @@ mod tests {
|
||||
let mut state = ServerState::new(ServerConfig::default(), [0u8; 32], pty_tx);
|
||||
|
||||
state
|
||||
.ensure_session("forward", 80, 24, "forward-only")
|
||||
.ensure_session("forward", 80, 24, "forward-only", &[])
|
||||
.unwrap();
|
||||
|
||||
assert!(state.sessions["forward"].pty.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn accepted_env_filters_by_server_patterns() {
|
||||
let config = ServerConfig {
|
||||
accept_env: vec!["LANG".to_string(), "LC_*".to_string()],
|
||||
..ServerConfig::default()
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
accepted_env(
|
||||
&config,
|
||||
&[
|
||||
EnvVar {
|
||||
name: "LANG".to_string(),
|
||||
value: "en_US.UTF-8".to_string()
|
||||
},
|
||||
EnvVar {
|
||||
name: "LC_TIME".to_string(),
|
||||
value: "C".to_string()
|
||||
},
|
||||
EnvVar {
|
||||
name: "SECRET_TOKEN".to_string(),
|
||||
value: "nope".to_string()
|
||||
}
|
||||
]
|
||||
)
|
||||
.unwrap(),
|
||||
vec![
|
||||
("LANG".to_string(), "en_US.UTF-8".to_string()),
|
||||
("LC_TIME".to_string(), "C".to_string())
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn accepted_env_rejects_invalid_names() {
|
||||
let err = accepted_env(
|
||||
&ServerConfig::default(),
|
||||
&[EnvVar {
|
||||
name: "BAD-NAME".to_string(),
|
||||
value: "value".to_string(),
|
||||
}],
|
||||
)
|
||||
.unwrap_err();
|
||||
|
||||
assert!(
|
||||
err.to_string()
|
||||
.contains("invalid environment variable name")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user