diff options
author | Jesse Luehrs <doy@tozt.net> | 2019-10-20 12:59:02 -0400 |
---|---|---|
committer | Jesse Luehrs <doy@tozt.net> | 2019-10-20 12:59:02 -0400 |
commit | 1fd0154711eadcd77c36bf6fc55917be54949f6a (patch) | |
tree | 1702186dac1f7f4be3295fd9d53f7dd72477cee6 /src | |
parent | ce48e85996f81a9a20846217bb5c150f7745b082 (diff) | |
download | teleterm-1fd0154711eadcd77c36bf6fc55917be54949f6a.tar.gz teleterm-1fd0154711eadcd77c36bf6fc55917be54949f6a.zip |
drop root privileges during normal operation
it should only be necessary for reading the tls key and binding to
low-numbered ports
Diffstat (limited to 'src')
-rw-r--r-- | src/cmd/server.rs | 25 | ||||
-rw-r--r-- | src/config.rs | 119 | ||||
-rw-r--r-- | src/error.rs | 18 | ||||
-rw-r--r-- | src/main.rs | 1 |
4 files changed, 163 insertions, 0 deletions
diff --git a/src/cmd/server.rs b/src/cmd/server.rs index 6e98fe5..a6b8cff 100644 --- a/src/cmd/server.rs +++ b/src/cmd/server.rs @@ -35,6 +35,8 @@ impl crate::config::Config for Config { tls_identity_file, self.server.allowed_login_methods.clone(), self.oauth_configs.clone(), + self.server.uid, + self.server.gid, )? } else { create_server( @@ -43,6 +45,8 @@ impl crate::config::Config for Config { self.server.read_timeout, self.server.allowed_login_methods.clone(), self.oauth_configs.clone(), + self.server.uid, + self.server.gid, )? }; tokio::run(futures::future::lazy(move || { @@ -82,6 +86,8 @@ fn create_server( crate::protocol::AuthType, crate::oauth::Config, >, + uid: Option<users::uid_t>, + gid: Option<users::gid_t>, ) -> Result<( Box<dyn futures::future::Future<Item = (), Error = Error> + Send>, Box<dyn futures::future::Future<Item = (), Error = Error> + Send>, @@ -89,6 +95,7 @@ fn create_server( let (mut sock_w, sock_r) = tokio::sync::mpsc::channel(100); let listener = tokio::net::TcpListener::bind(&address) .context(crate::error::Bind { address })?; + drop_privs(uid, gid)?; let acceptor = listener .incoming() .context(crate::error::Acceptor) @@ -119,6 +126,8 @@ fn create_server_tls( crate::protocol::AuthType, crate::oauth::Config, >, + uid: Option<users::uid_t>, + gid: Option<users::gid_t>, ) -> Result<( Box<dyn futures::future::Future<Item = (), Error = Error> + Send>, Box<dyn futures::future::Future<Item = (), Error = Error> + Send>, @@ -126,6 +135,7 @@ fn create_server_tls( let (mut sock_w, sock_r) = tokio::sync::mpsc::channel(100); let listener = tokio::net::TcpListener::bind(&address) .context(crate::error::Bind { address })?; + drop_privs(uid, gid)?; let mut file = std::fs::File::open(tls_identity_file).context( crate::error::OpenFileSync { @@ -159,3 +169,18 @@ fn create_server_tls( ); Ok((Box::new(acceptor), Box::new(server))) } + +fn drop_privs( + uid: Option<users::uid_t>, + gid: Option<users::gid_t>, +) -> Result<()> { + if let Some(gid) = gid { + users::switch::set_effective_gid(gid) + .context(crate::error::SwitchGid)?; + } + if let Some(uid) = uid { + users::switch::set_effective_uid(uid) + .context(crate::error::SwitchUid)?; + } + Ok(()) +} diff --git a/src/config.rs b/src/config.rs index 3a22085..dcc44d7 100644 --- a/src/config.rs +++ b/src/config.rs @@ -222,6 +222,12 @@ pub struct Server { )] pub allowed_login_methods: std::collections::HashSet<crate::protocol::AuthType>, + + #[serde(deserialize_with = "uid", default)] + pub uid: Option<users::uid_t>, + + #[serde(deserialize_with = "gid", default)] + pub gid: Option<users::gid_t>, } impl Server { @@ -307,6 +313,8 @@ impl Default for Server { read_timeout: default_read_timeout(), tls_identity_file: None, allowed_login_methods: default_allowed_login_methods(), + uid: None, + gid: None, } } } @@ -410,6 +418,117 @@ fn default_allowed_login_methods( crate::protocol::AuthType::iter().collect() } +fn uid<'a, D>( + deserializer: D, +) -> std::result::Result<Option<users::uid_t>, D::Error> +where + D: serde::de::Deserializer<'a>, +{ + struct StringOrInt; + + impl<'a> serde::de::Visitor<'a> for StringOrInt { + type Value = Option<u32>; + + fn expecting( + &self, + formatter: &mut std::fmt::Formatter, + ) -> std::fmt::Result { + formatter.write_str("string or int") + } + + fn visit_str<E>( + self, + value: &str, + ) -> std::result::Result<Self::Value, E> + where + E: serde::de::Error, + { + Ok(Some( + users::get_user_by_name(value) + .context(crate::error::UnknownUser { name: value }) + .map_err(serde::de::Error::custom)? + .uid(), + )) + } + + fn visit_u32<E>( + self, + value: u32, + ) -> std::result::Result<Self::Value, E> + where + E: serde::de::Error, + { + if users::get_user_by_uid(value).is_none() { + return Err(serde::de::Error::custom(Error::UnknownUid { + uid: value, + })); + } + Ok(Some(value)) + } + } + + deserializer.deserialize_any(StringOrInt) +} + +fn gid<'a, D>( + deserializer: D, +) -> std::result::Result<Option<users::gid_t>, D::Error> +where + D: serde::de::Deserializer<'a>, +{ + struct StringOrInt; + + impl<'a> serde::de::Visitor<'a> for StringOrInt { + type Value = Option<u32>; + + fn expecting( + &self, + formatter: &mut std::fmt::Formatter, + ) -> std::fmt::Result { + formatter.write_str("string or int") + } + + fn visit_none<E>(self) -> std::result::Result<Self::Value, E> + where + E: serde::de::Error, + { + Ok(None) + } + + fn visit_str<E>( + self, + value: &str, + ) -> std::result::Result<Self::Value, E> + where + E: serde::de::Error, + { + Ok(Some( + users::get_group_by_name(value) + .context(crate::error::UnknownGroup { name: value }) + .map_err(serde::de::Error::custom)? + .gid(), + )) + } + + fn visit_u32<E>( + self, + value: u32, + ) -> std::result::Result<Self::Value, E> + where + E: serde::de::Error, + { + if users::get_group_by_gid(value).is_none() { + return Err(serde::de::Error::custom(Error::UnknownGid { + gid: value, + })); + } + Ok(Some(value)) + } + } + + deserializer.deserialize_any(StringOrInt) +} + #[derive(serde::Deserialize, Debug)] pub struct Command { #[serde(default = "default_buffer_size")] diff --git a/src/error.rs b/src/error.rs index eeca1e8..a02b836 100644 --- a/src/error.rs +++ b/src/error.rs @@ -347,6 +347,12 @@ pub enum Error { #[snafu(display("failed to spawn process for `{}`: {}", cmd, source))] SpawnProcess { cmd: String, source: std::io::Error }, + #[snafu(display("failed to switch gid: {}", source))] + SwitchGid { source: std::io::Error }, + + #[snafu(display("failed to switch uid: {}", source))] + SwitchUid { source: std::io::Error }, + #[snafu(display( "failed to spawn a background thread to read terminal input: {}", source @@ -386,6 +392,18 @@ pub enum Error { #[snafu(display("unexpected message: {:?}", message))] UnexpectedMessage { message: crate::protocol::Message }, + #[snafu(display("failed to find group with gid {}", gid))] + UnknownGid { gid: users::gid_t }, + + #[snafu(display("failed to find group with group name {}", name))] + UnknownGroup { name: String }, + + #[snafu(display("failed to find user with uid {}", uid))] + UnknownUid { uid: users::uid_t }, + + #[snafu(display("failed to find user with username {}", name))] + UnknownUser { name: String }, + #[snafu(display("failed to write to event channel: {}", source))] WriteChannel { source: tokio::sync::mpsc::error::UnboundedSendError, diff --git a/src/main.rs b/src/main.rs index c088d6d..ed241d8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,6 +5,7 @@ #![allow(clippy::similar_names)] #![allow(clippy::single_match)] #![allow(clippy::single_match_else)] +#![allow(clippy::too_many_arguments)] #![allow(clippy::type_complexity)] mod prelude; |