diff --git a/Cargo.lock b/Cargo.lock index 4ed90da..e61ad9f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3,6 +3,7 @@ name = "redox-ssh" version = "0.1.0" dependencies = [ "byteorder 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.26 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", "num-bigint 0.1.39 (git+https://github.com/rust-num/num)", "rand 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/Cargo.toml b/Cargo.toml index 8563a50..b5ba058 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,6 +29,9 @@ rust-crypto = "^0.2" rand = "^0.3" num-bigint = { git = "https://github.com/rust-num/num" } +[target.'cfg(not(target_os = "redox"))'.dependencies] +libc = "^0.2.26" + [target.'cfg(target_os = "redox")'.dependencies] redox_syscall = "0.1" diff --git a/src/bin/sshd.rs b/src/bin/sshd.rs index 4f34745..7241698 100644 --- a/src/bin/sshd.rs +++ b/src/bin/sshd.rs @@ -29,6 +29,7 @@ impl log::Log for StdErrLogger { pub fn main() { let mut verbose = false; + let mut foreground = false; let key_pair = File::open("server.key").and_then( |mut f| (ED25519.import)(&mut f), @@ -51,6 +52,7 @@ pub fn main() { match arg.as_ref() { "-v" => verbose = true, + "-f" => foreground = true, "-p" => { config.port = u16::from_str( @@ -68,6 +70,13 @@ pub fn main() { }).unwrap(); } + if !foreground { + use ssh::sys::fork; + if fork() != 0 { + process::exit(0); + } + } + let server = Server::with_config(config); if let Err(err) = server.run() { diff --git a/src/channel.rs b/src/channel.rs new file mode 100644 index 0000000..f4ce52d --- /dev/null +++ b/src/channel.rs @@ -0,0 +1,131 @@ +use std::fs::{File, OpenOptions}; +use std::io::{self, Read, Write}; +use std::os::unix::io::{AsRawFd, FromRawFd, RawFd}; +use std::os::unix::process::CommandExt; +use std::path::PathBuf; +use std::process::{self, Stdio}; +use sys::{before_exec, getpty}; + +pub type ChannelId = u32; + +#[derive(Debug)] +pub struct Channel { + id: ChannelId, + peer_id: ChannelId, + process: Option, + pty: Option<(RawFd, PathBuf)>, + master: Option, + stdio: Option<(File, File, File)>, + window_size: u32, + peer_window_size: u32, + max_packet_size: u32, +} + +#[derive(Debug)] +pub enum ChannelRequest { + Pty { + term: String, + char_width: u32, + row_height: u32, + pixel_width: u32, + pixel_height: u32, + modes: Vec, + }, + Shell, +} + +impl Channel { + pub fn new( + id: ChannelId, peer_id: ChannelId, peer_window_size: u32, + max_packet_size: u32 + ) -> Channel { + Channel { + id: id, + peer_id: peer_id, + process: None, + master: None, + pty: None, + stdio: None, + window_size: peer_window_size, + peer_window_size: peer_window_size, + max_packet_size: max_packet_size, + } + } + + pub fn id(&self) -> ChannelId { + self.id + } + pub fn window_size(&self) -> u32 { + self.window_size + } + pub fn max_packet_size(&self) -> u32 { + self.max_packet_size + } + + pub fn request(&mut self, request: ChannelRequest) { + match request + { + ChannelRequest::Pty { .. } => { + let (master_fd, tty_path) = getpty(); + + let stdin = OpenOptions::new() + .read(true) + .write(true) + .open(&tty_path) + .unwrap(); + + let stdout = OpenOptions::new() + .read(true) + .write(true) + .open(&tty_path) + .unwrap(); + + let stderr = OpenOptions::new() + .read(true) + .write(true) + .open(&tty_path) + .unwrap(); + + self.stdio = Some((stdin, stdout, stderr)); + self.master = Some(unsafe { File::from_raw_fd(master_fd) }); + } + ChannelRequest::Shell => { + if let Some((ref stdin, ref stdout, ref stderr)) = self.stdio { + process::Command::new("login") + .stdin(unsafe { Stdio::from_raw_fd(stdin.as_raw_fd()) }) + .stdout( + unsafe { Stdio::from_raw_fd(stdout.as_raw_fd()) }, + ) + .stderr( + unsafe { Stdio::from_raw_fd(stderr.as_raw_fd()) }, + ) + .before_exec(|| before_exec()) + .spawn() + .unwrap(); + } + } + } + debug!("Channel Request: {:?}", request); + } + + pub fn data(&mut self, data: &[u8]) -> io::Result<()> { + if let Some(ref mut master) = self.master { + master.write_all(data)?; + master.flush() + } + else { + Ok(()) + } + } + + pub fn read(&mut self) -> io::Result> { + if let Some(ref mut master) = self.master { + let mut buf = [0; 4096]; + let count = master.read(&mut buf)?; + Ok(buf[0..count].to_vec()) + } + else { + Ok(b"".to_vec()) + } + } +} diff --git a/src/connection.rs b/src/connection.rs index eb1ec61..0fc3559 100644 --- a/src/connection.rs +++ b/src/connection.rs @@ -1,8 +1,9 @@ -use std::collections::VecDeque; +use std::collections::{BTreeMap, VecDeque}; use std::io::{self, BufReader, Read, Write}; use std::net::TcpStream; use std::sync::Arc; +use channel::{Channel, ChannelId, ChannelRequest}; use encryption::{AesCtr, Decryptor, Encryption}; use error::{ConnectionError, ConnectionResult as Result}; use key_exchange::{KexResult, KeyExchange}; @@ -41,6 +42,7 @@ pub struct Connection { mac: Option<(Box, Box)>, seq: (u32, u32), tx_queue: VecDeque, + channels: BTreeMap, } impl<'a> Connection { @@ -55,6 +57,7 @@ impl<'a> Connection { mac: None, seq: (0, 0), tx_queue: VecDeque::new(), + channels: BTreeMap::new(), } } @@ -103,7 +106,7 @@ impl<'a> Connection { } } - trace!("Packet {} received: {:?}", self.seq.0, packet); + debug!("Packet {} received: {:?}", self.seq.0, packet); // Count up the received packet sequence number self.seq.0 = self.seq.0.wrapping_add(1); @@ -113,7 +116,7 @@ impl<'a> Connection { fn send(&mut self, mut stream: &mut Write, packet: Packet) -> io::Result<()> { - trace!("Sending packet {}: {:?}", self.seq.1, packet); + debug!("Sending packet {}: {:?}", self.seq.1, packet); let packet = packet.to_raw()?; @@ -247,7 +250,10 @@ impl<'a> Connection { let mut reader = packet.reader(); let name = reader.read_string()?; - trace!("{:?}", ::std::str::from_utf8(&name.as_slice()).unwrap()); + trace!( + "Service Request {:?}", + ::std::str::from_utf8(&name.as_slice()).unwrap() + ); let mut res = Packet::new(MessageType::ServiceAccept); res.with_writer(&|w| { @@ -292,64 +298,63 @@ impl<'a> Connection { fn channel_open(&mut self, packet: Packet) -> Result> { let mut reader = packet.reader(); let channel_type = reader.read_utf8()?; - let sender_channel = reader.read_uint32()?; + let peer_id = reader.read_uint32()?; let window_size = reader.read_uint32()?; let max_packet_size = reader.read_uint32()?; + let id = if let Some((id, chan)) = self.channels.iter().next_back() { + id + 1 + } + else { + 0 + }; + + let channel = Channel::new(id, peer_id, window_size, max_packet_size); + let mut res = Packet::new(MessageType::ChannelOpenConfirmation); res.with_writer(&|w| { - w.write_uint32(sender_channel)?; - w.write_uint32(0)?; - w.write_uint32(window_size)?; - w.write_uint32(max_packet_size)?; + w.write_uint32(peer_id)?; + w.write_uint32(channel.id())?; + w.write_uint32(channel.window_size())?; + w.write_uint32(channel.max_packet_size())?; Ok(()) })?; - debug!( - "Channel Open {:?}, {:?}, {:?}, {:?}", - channel_type, - sender_channel, - window_size, - max_packet_size - ); + debug!("Open {:?}", channel); + + self.channels.insert(id, channel); Ok(Some(res)) } fn channel_request(&mut self, packet: Packet) -> Result> { let mut reader = packet.reader(); - let channel = reader.read_uint32()?; - let request = reader.read_utf8()?; + let channel_id = reader.read_uint32()?; + let name = reader.read_utf8()?; let want_reply = reader.read_bool()?; - debug!( - "Channel Request {:?}, {:?}, {:?}", - channel, - request, - want_reply - ); - if request == "pty-req" { - let term = reader.read_utf8()?; - let char_width = reader.read_uint32()?; - let row_height = reader.read_uint32()?; - let pixel_width = reader.read_uint32()?; - let pixel_height = reader.read_uint32()?; - let modes = reader.read_string()?; + let request = match &*name + { + "pty-req" => Some(ChannelRequest::Pty { + term: reader.read_utf8()?, + char_width: reader.read_uint32()?, + row_height: reader.read_uint32()?, + pixel_width: reader.read_uint32()?, + pixel_height: reader.read_uint32()?, + modes: reader.read_string()?, + }), + "shell" => Some(ChannelRequest::Shell), + _ => None, + }; - debug!( - "PTY request: {:?} {:?} {:?} {:?} {:?} {:?}", - term, - char_width, - row_height, - pixel_width, - pixel_height, - modes - ); + + if let Some(request) = request { + let mut channel = self.channels.get_mut(&channel_id).unwrap(); + channel.request(request); } - - if request == "shell" { - debug!("Shell request"); + else { + warn!("Unkown channel request {}", name); } if want_reply { @@ -364,24 +369,28 @@ impl<'a> Connection { fn channel_data(&mut self, packet: Packet) -> Result> { let mut reader = packet.reader(); - let channel = reader.read_uint32()?; + let channel_id = reader.read_uint32()?; let data = reader.read_string()?; - let mut res = Packet::new(MessageType::ChannelData); - res.with_writer(&|w| { - w.write_uint32(0)?; - w.write_bytes(data.as_slice())?; - Ok(()) - })?; + let mut channel = self.channels.get_mut(&channel_id).unwrap(); + channel.data(data.as_slice())?; - debug!( - "Channel {} Data ({} bytes): {:?}", - channel, - data.len(), - data - ); + let data = channel.read()?; + + if data.len() > 0 { + let mut res = Packet::new(MessageType::ChannelData); + res.with_writer(&|w| { + w.write_uint32(0)?; + w.write_bytes(data.as_slice())?; + Ok(()) + })?; + + Ok(Some(res)) + } + else { + Ok(None) + } - Ok(Some(res)) } fn kex_init(&mut self, packet: Packet) -> Result> { diff --git a/src/encryption/aes_ctr.rs b/src/encryption/aes_ctr.rs index 75df3dc..36d812a 100644 --- a/src/encryption/aes_ctr.rs +++ b/src/encryption/aes_ctr.rs @@ -15,12 +15,10 @@ impl AesCtr { impl Encryption for AesCtr { fn encrypt(&mut self, data: &[u8], buf: &mut [u8]) { - trace!("Encrypting {} -> {}", data.len(), buf.len()); self.cipher.process(data, buf); } fn decrypt(&mut self, data: &[u8], buf: &mut [u8]) { - trace!("Decrypting {} -> {}", data.len(), buf.len()); self.cipher.process(data, buf); } } diff --git a/src/lib.rs b/src/lib.rs index 1785fd9..cc4eed8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,6 +4,10 @@ extern crate crypto; extern crate num_bigint; #[macro_use] extern crate log; +#[cfg(target_os = "redox")] +extern crate syscall; +#[cfg(not(target_os = "redox"))] +extern crate libc; mod error; mod algorithm; @@ -13,8 +17,17 @@ mod connection; mod key_exchange; mod encryption; mod mac; +mod channel; pub mod public_key; pub mod server; +#[cfg(target_os = "redox")] +#[path = "sys/redox.rs"] +pub mod sys; + +#[cfg(not(target_os = "redox"))] +#[path = "sys/unix.rs"] +pub mod sys; + pub use self::server::{Server, ServerConfig}; diff --git a/src/sys/redox.rs b/src/sys/redox.rs new file mode 100644 index 0000000..247f52e --- /dev/null +++ b/src/sys/redox.rs @@ -0,0 +1,31 @@ +use std::io::Result; +use std::os::unix::io::RawFd; +use std::path::PathBuf; + +pub fn before_exec() -> Result<()> { + Ok(()) +} + +pub fn fork() -> usize { + extern crate syscall; + unsafe { syscall::clone(0).unwrap() } +} + +pub fn getpty() -> (RawFd, PathBuf) { + use syscall; + + let master = syscall::open( + "pty:", + syscall::O_RDWR | syscall::O_CREAT | syscall::O_NONBLOCK, + ).unwrap(); + + let mut buf: [u8; 4096] = [0; 4096]; + + let count = syscall::fpath(master, &mut buf).unwrap(); + ( + master, + PathBuf::from(unsafe { + String::from_utf8_unchecked(Vec::from(&buf[..count])) + }), + ) +} diff --git a/src/sys/unix.rs b/src/sys/unix.rs new file mode 100644 index 0000000..9015402 --- /dev/null +++ b/src/sys/unix.rs @@ -0,0 +1,64 @@ +use std::io::Result; +use std::os::unix::io::RawFd; +use std::path::PathBuf; + +pub fn before_exec() -> Result<()> { + use libc; + unsafe { + libc::setsid(); + libc::ioctl(0, libc::TIOCSCTTY, 1); + } + Ok(()) +} + +pub fn fork() -> usize { + extern crate libc; + unsafe { libc::fork() as usize } +} + +pub fn getpty() -> (RawFd, PathBuf) { + use libc; + use std::ffi::CStr; + use std::fs::OpenOptions; + use std::io::Error; + use std::os::unix::fs::OpenOptionsExt; + use std::os::unix::io::IntoRawFd; + + const TIOCPKT: libc::c_ulong = 0x5420; + extern "C" { + fn ptsname(fd: libc::c_int) -> *const libc::c_char; + fn grantpt(fd: libc::c_int) -> libc::c_int; + fn unlockpt(fd: libc::c_int) -> libc::c_int; + fn ioctl(fd: libc::c_int, request: libc::c_ulong, ...) -> libc::c_int; + } + + let master_fd = OpenOptions::new() + .read(true) + .write(true) + .custom_flags(libc::O_NONBLOCK) + .open("/dev/ptmx") + .unwrap() + .into_raw_fd(); + + unsafe { + let mut flag: libc::c_int = 1; + if ioctl(master_fd, TIOCPKT, &mut flag as *mut libc::c_int) < 0 { + panic!("ioctl: {:?}", Error::last_os_error()); + } + if grantpt(master_fd) < 0 { + panic!("grantpt: {:?}", Error::last_os_error()); + } + if unlockpt(master_fd) < 0 { + panic!("unlockpt: {:?}", Error::last_os_error()); + } + } + + let tty_path = unsafe { + PathBuf::from( + CStr::from_ptr(ptsname(master_fd)) + .to_string_lossy() + .into_owned(), + ) + }; + (master_fd, tty_path) +}