mirror of
https://gitlab.redox-os.org/CoffeeCode/redox-ssh.git
synced 2025-12-28 17:02:19 +01:00
Implement channels
This commit is contained in:
parent
8c911fec23
commit
af8a1ffb77
9 changed files with 318 additions and 59 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
|
@ -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)",
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
131
src/channel.rs
Normal file
131
src/channel.rs
Normal file
|
|
@ -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<process::Child>,
|
||||
pty: Option<(RawFd, PathBuf)>,
|
||||
master: Option<File>,
|
||||
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<u8>,
|
||||
},
|
||||
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<Vec<u8>> {
|
||||
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())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<MacAlgorithm>, Box<MacAlgorithm>)>,
|
||||
seq: (u32, u32),
|
||||
tx_queue: VecDeque<Packet>,
|
||||
channels: BTreeMap<ChannelId, Channel>,
|
||||
}
|
||||
|
||||
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<Option<Packet>> {
|
||||
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<Option<Packet>> {
|
||||
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<Option<Packet>> {
|
||||
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<Option<Packet>> {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
13
src/lib.rs
13
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};
|
||||
|
|
|
|||
31
src/sys/redox.rs
Normal file
31
src/sys/redox.rs
Normal file
|
|
@ -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]))
|
||||
}),
|
||||
)
|
||||
}
|
||||
64
src/sys/unix.rs
Normal file
64
src/sys/unix.rs
Normal file
|
|
@ -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)
|
||||
}
|
||||
Loading…
Reference in a new issue