mirror of
https://github.com/Noratrieb/cluelessh.git
synced 2026-01-15 08:55:06 +01:00
refactor
This commit is contained in:
parent
ae5db1642c
commit
0efd08dd5c
12 changed files with 268 additions and 155 deletions
|
|
@ -1,218 +0,0 @@
|
|||
use std::collections::VecDeque;
|
||||
use tracing::{debug, warn};
|
||||
|
||||
use crate::client_error;
|
||||
use crate::packet::Packet;
|
||||
use crate::parse::{Parser, Writer};
|
||||
use crate::Result;
|
||||
|
||||
pub(crate) struct ServerChannelsState {
|
||||
packets_to_send: VecDeque<Packet>,
|
||||
channels: Vec<SessionChannel>,
|
||||
|
||||
channel_updates: VecDeque<ChannelUpdate>,
|
||||
}
|
||||
|
||||
struct SessionChannel {
|
||||
/// Whether our side has closed this channel.
|
||||
we_closed: bool,
|
||||
peer_channel: u32,
|
||||
has_pty: bool,
|
||||
has_shell: bool,
|
||||
sent_bytes: Vec<u8>,
|
||||
}
|
||||
|
||||
pub struct ChannelUpdate {
|
||||
pub channel: u32,
|
||||
pub kind: ChannelUpdateKind,
|
||||
}
|
||||
|
||||
pub enum ChannelUpdateKind {
|
||||
ChannelData(Vec<u8>),
|
||||
}
|
||||
|
||||
impl ServerChannelsState {
|
||||
pub(crate) fn new() -> Self {
|
||||
ServerChannelsState {
|
||||
packets_to_send: VecDeque::new(),
|
||||
channels: Vec::new(),
|
||||
channel_updates: VecDeque::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn on_packet(&mut self, packet_type: u8, mut payload: Parser<'_>) -> Result<()> {
|
||||
match packet_type {
|
||||
Packet::SSH_MSG_CHANNEL_OPEN => {
|
||||
// <https://datatracker.ietf.org/doc/html/rfc4254#section-5.1>
|
||||
let channel_type = payload.utf8_string()?;
|
||||
let sender_channel = payload.u32()?;
|
||||
let initial_window_size = payload.u32()?;
|
||||
let max_packet_size = payload.u32()?;
|
||||
|
||||
debug!(?channel_type, ?sender_channel, "Opening channel");
|
||||
|
||||
match channel_type {
|
||||
"session" => {
|
||||
let our_number = self.channels.len() as u32;
|
||||
|
||||
self.packets_to_send
|
||||
.push_back(Packet::new_msg_channel_open_confirmation(
|
||||
our_number,
|
||||
sender_channel,
|
||||
initial_window_size,
|
||||
max_packet_size,
|
||||
));
|
||||
|
||||
self.channels.push(SessionChannel {
|
||||
we_closed: false,
|
||||
peer_channel: sender_channel,
|
||||
has_pty: false,
|
||||
has_shell: false,
|
||||
sent_bytes: Vec::new(),
|
||||
});
|
||||
|
||||
debug!(?channel_type, ?our_number, "Successfully opened channel");
|
||||
}
|
||||
_ => {
|
||||
self.packets_to_send
|
||||
.push_back(Packet::new_msg_channel_open_failure(
|
||||
sender_channel,
|
||||
3, // SSH_OPEN_UNKNOWN_CHANNEL_TYPE
|
||||
b"unknown channel type",
|
||||
b"",
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
Packet::SSH_MSG_CHANNEL_DATA => {
|
||||
let our_channel = payload.u32()?;
|
||||
let data = payload.string()?;
|
||||
|
||||
let channel = self.channel(our_channel)?;
|
||||
channel.recv_bytes(data);
|
||||
|
||||
let peer = channel.peer_channel;
|
||||
// echo :3
|
||||
self.packets_to_send
|
||||
.push_back(Packet::new_msg_channel_data(peer, data));
|
||||
|
||||
if data.contains(&0x03 /*EOF, Ctrl-C*/) {
|
||||
debug!(?our_channel, "Received EOF, closing channel");
|
||||
// <https://datatracker.ietf.org/doc/html/rfc4254#section-5.3>
|
||||
self.packets_to_send
|
||||
.push_back(Packet::new_msg_channel_close(peer));
|
||||
|
||||
let channel = self.channel(our_channel)?;
|
||||
channel.we_closed = true;
|
||||
}
|
||||
}
|
||||
Packet::SSH_MSG_CHANNEL_CLOSE => {
|
||||
// <https://datatracker.ietf.org/doc/html/rfc4254#section-5.3>
|
||||
let our_channel = payload.u32()?;
|
||||
let channel = self.channel(our_channel)?;
|
||||
if !channel.we_closed {
|
||||
let close = Packet::new_msg_channel_close(channel.peer_channel);
|
||||
self.packets_to_send.push_back(close);
|
||||
}
|
||||
|
||||
self.channels.remove(our_channel as usize);
|
||||
|
||||
debug!("Channel has been closed");
|
||||
}
|
||||
Packet::SSH_MSG_CHANNEL_REQUEST => {
|
||||
let our_channel = payload.u32()?;
|
||||
let request_type = payload.utf8_string()?;
|
||||
let want_reply = payload.bool()?;
|
||||
|
||||
debug!(?our_channel, ?request_type, "Got channel request");
|
||||
|
||||
let channel = self.channel(our_channel)?;
|
||||
let peer_channel = channel.peer_channel;
|
||||
|
||||
match request_type {
|
||||
"pty-req" => {
|
||||
let term = payload.utf8_string()?;
|
||||
let width_chars = payload.u32()?;
|
||||
let height_rows = payload.u32()?;
|
||||
let _width_px = payload.u32()?;
|
||||
let _height_px = payload.u32()?;
|
||||
let _term_modes = payload.string()?;
|
||||
|
||||
debug!(
|
||||
?our_channel,
|
||||
?term,
|
||||
?width_chars,
|
||||
?height_rows,
|
||||
"Trying to open a terminal"
|
||||
);
|
||||
|
||||
// Faithfully allocate the PTY.
|
||||
channel.has_pty = true;
|
||||
|
||||
if want_reply {
|
||||
self.send_channel_success(peer_channel);
|
||||
}
|
||||
}
|
||||
"shell" => {
|
||||
if !channel.has_pty {
|
||||
self.send_channel_failure(peer_channel);
|
||||
}
|
||||
|
||||
// Sure! (reborrow)
|
||||
let channel = self.channel(our_channel)?;
|
||||
channel.has_shell = true;
|
||||
|
||||
debug!(?our_channel, "Opening shell");
|
||||
|
||||
if want_reply {
|
||||
self.send_channel_success(peer_channel);
|
||||
}
|
||||
}
|
||||
"signal" => {
|
||||
debug!(?our_channel, "Received signal");
|
||||
// Ignore signals, something we can do.
|
||||
}
|
||||
_ => {
|
||||
warn!(?request_type, ?our_channel, "Unknown channel request");
|
||||
self.send_channel_failure(peer_channel);
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
todo!("{packet_type}");
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn packets_to_send(&mut self) -> impl Iterator<Item = Packet> + '_ {
|
||||
self.packets_to_send.drain(..)
|
||||
}
|
||||
|
||||
pub(crate) fn channel_updates(&mut self) -> impl Iterator<Item = ChannelUpdate> + '_ {
|
||||
self.channel_updates.drain(..)
|
||||
}
|
||||
|
||||
fn send_channel_success(&mut self, recipient_channel: u32) {
|
||||
self.packets_to_send
|
||||
.push_back(Packet::new_msg_channel_success(recipient_channel));
|
||||
}
|
||||
|
||||
fn send_channel_failure(&mut self, recipient_channel: u32) {
|
||||
self.packets_to_send
|
||||
.push_back(Packet::new_msg_channel_failure(recipient_channel));
|
||||
}
|
||||
|
||||
fn channel(&mut self, number: u32) -> Result<&mut SessionChannel> {
|
||||
self.channels
|
||||
.get_mut(number as usize)
|
||||
.ok_or_else(|| client_error!("unknown channel: {number}"))
|
||||
}
|
||||
}
|
||||
|
||||
impl SessionChannel {
|
||||
fn recv_bytes(&mut self, bytes: &[u8]) {
|
||||
self.sent_bytes.extend_from_slice(bytes);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,3 @@
|
|||
mod channel;
|
||||
mod keys;
|
||||
pub mod packet;
|
||||
pub mod parse;
|
||||
|
|
@ -6,7 +5,6 @@ pub mod parse;
|
|||
use core::str;
|
||||
use std::{collections::VecDeque, mem::take};
|
||||
|
||||
use channel::ServerChannelsState;
|
||||
use ed25519_dalek::ed25519::signature::Signer;
|
||||
use packet::{
|
||||
DhKeyExchangeInitPacket, DhKeyExchangeInitReplyPacket, KeyExchangeInitPacket, Packet,
|
||||
|
|
@ -18,7 +16,6 @@ use sha2::Digest;
|
|||
use tracing::{debug, info, trace};
|
||||
use x25519_dalek::{EphemeralSecret, PublicKey};
|
||||
|
||||
pub use channel::ChannelUpdate;
|
||||
pub use packet::Msg;
|
||||
|
||||
#[derive(Debug)]
|
||||
|
|
@ -52,7 +49,7 @@ pub struct ServerConnection {
|
|||
packet_transport: PacketTransport,
|
||||
rng: Box<dyn SshRng + Send + Sync>,
|
||||
|
||||
channel_updates: VecDeque<ChannelUpdate>,
|
||||
plaintext_packets: VecDeque<Packet>,
|
||||
}
|
||||
|
||||
enum ServerState {
|
||||
|
|
@ -72,14 +69,7 @@ enum ServerState {
|
|||
k: [u8; 32],
|
||||
},
|
||||
ServiceRequest,
|
||||
// At this point we transfer to <https://datatracker.ietf.org/doc/html/rfc4252>
|
||||
UserAuthRequest {
|
||||
/// Whether the client has failed already (by sending the wrong method).
|
||||
// The second failure results in disconnecting.
|
||||
has_failed: bool,
|
||||
},
|
||||
/// The connection has been opened, all connection-related messages are delegated to the connection handler.
|
||||
ConnectionOpen(ServerChannelsState),
|
||||
Open,
|
||||
}
|
||||
|
||||
pub trait SshRng {
|
||||
|
|
@ -121,7 +111,8 @@ impl ServerConnection {
|
|||
},
|
||||
packet_transport: PacketTransport::new(),
|
||||
rng: Box::new(rng),
|
||||
channel_updates: VecDeque::new(),
|
||||
|
||||
plaintext_packets: VecDeque::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -343,6 +334,7 @@ impl ServerConnection {
|
|||
self.packet_transport.set_key(h, k);
|
||||
}
|
||||
ServerState::ServiceRequest => {
|
||||
// TODO: this should probably move out of here? unsure.
|
||||
if packet.payload.first() != Some(&Packet::SSH_MSG_SERVICE_REQUEST) {
|
||||
return Err(client_error!("did not send SSH_MSG_SERVICE_REQUEST"));
|
||||
}
|
||||
|
|
@ -362,107 +354,10 @@ impl ServerConnection {
|
|||
writer.finish()
|
||||
},
|
||||
});
|
||||
self.state = ServerState::UserAuthRequest { has_failed: false };
|
||||
self.state = ServerState::Open;
|
||||
}
|
||||
ServerState::UserAuthRequest { has_failed } => {
|
||||
// This is a super simplistic implementation of RFC4252 SSH authentication.
|
||||
// We ask for a public key, and always let that one pass.
|
||||
// The reason for this is that this makes it a lot easier to test locally.
|
||||
// It's not very good, but it's good enough for now.
|
||||
let mut auth_req = packet.payload_parser();
|
||||
|
||||
if auth_req.u8()? != Packet::SSH_MSG_USERAUTH_REQUEST {
|
||||
return Err(client_error!("did not send SSH_MSG_SERVICE_REQUEST"));
|
||||
}
|
||||
let username = auth_req.utf8_string()?;
|
||||
let service_name = auth_req.utf8_string()?;
|
||||
let method_name = auth_req.utf8_string()?;
|
||||
|
||||
info!(
|
||||
?username,
|
||||
?service_name,
|
||||
?method_name,
|
||||
"User trying to authenticate"
|
||||
);
|
||||
|
||||
if service_name != "ssh-connection" {
|
||||
return Err(client_error!(
|
||||
"client tried to unsupported service: {service_name}"
|
||||
));
|
||||
}
|
||||
|
||||
match method_name {
|
||||
"password" => {
|
||||
let change_password = auth_req.bool()?;
|
||||
if change_password {
|
||||
return Err(client_error!(
|
||||
"client tried to change password unprompted"
|
||||
));
|
||||
}
|
||||
let password = auth_req.utf8_string()?;
|
||||
|
||||
info!(?password, "Got password");
|
||||
// Don't worry queen, your password is correct!
|
||||
self.packet_transport
|
||||
.queue_packet(Packet::new_msg_userauth_success());
|
||||
|
||||
self.state = ServerState::ConnectionOpen(ServerChannelsState::new());
|
||||
}
|
||||
"publickey" => {
|
||||
info!("Got public key");
|
||||
// Don't worry queen, your key is correct!
|
||||
self.packet_transport
|
||||
.queue_packet(Packet::new_msg_userauth_success());
|
||||
self.state = ServerState::ConnectionOpen(ServerChannelsState::new());
|
||||
}
|
||||
_ if *has_failed => {
|
||||
return Err(client_error!(
|
||||
"client tried unsupported method twice: {method_name}"
|
||||
));
|
||||
}
|
||||
_ => {
|
||||
// Initial.
|
||||
|
||||
self.packet_transport.queue_packet(Packet::new_msg_userauth_banner(
|
||||
b"!! this system ONLY allows catgirls to enter !!\r\n\
|
||||
!! all other attempts WILL be prosecuted to the full extent of the rawr !!\r\n",
|
||||
b"",
|
||||
));
|
||||
|
||||
self.packet_transport
|
||||
.queue_packet(Packet::new_msg_userauth_failure(
|
||||
NameList::one("publickey"),
|
||||
false,
|
||||
));
|
||||
// Stay in the same state
|
||||
}
|
||||
}
|
||||
}
|
||||
ServerState::ConnectionOpen(con) => {
|
||||
let mut payload = packet.payload_parser();
|
||||
let packet_type = payload.u8()?;
|
||||
|
||||
match packet_type {
|
||||
// Connection-related packets
|
||||
90..128 => {
|
||||
con.on_packet(packet_type, payload)?;
|
||||
for packet in con.packets_to_send() {
|
||||
self.packet_transport.queue_packet(packet);
|
||||
}
|
||||
self.channel_updates.extend(con.channel_updates());
|
||||
}
|
||||
Packet::SSH_MSG_GLOBAL_REQUEST => {
|
||||
let request_name = payload.utf8_string()?;
|
||||
let want_reply = payload.bool()?;
|
||||
debug!(?request_name, ?want_reply, "Received global request");
|
||||
|
||||
self.packet_transport
|
||||
.queue_packet(Packet::new_msg_request_failure());
|
||||
}
|
||||
_ => {
|
||||
todo!("packet: {packet_type}");
|
||||
}
|
||||
}
|
||||
ServerState::Open => {
|
||||
self.plaintext_packets.push_back(packet);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -473,8 +368,12 @@ impl ServerConnection {
|
|||
self.packet_transport.next_msg_to_send()
|
||||
}
|
||||
|
||||
pub fn next_channel_update(&mut self) -> Option<ChannelUpdate> {
|
||||
self.channel_updates.pop_front()
|
||||
pub fn next_plaintext_packet(&mut self) -> Option<Packet> {
|
||||
self.plaintext_packets.pop_front()
|
||||
}
|
||||
|
||||
pub fn send_plaintext_packet(&mut self, packet: Packet) {
|
||||
self.packet_transport.queue_packet(packet);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -119,16 +119,16 @@ impl Writer {
|
|||
pub struct NameList<'a>(&'a str);
|
||||
|
||||
impl<'a> NameList<'a> {
|
||||
pub(crate) fn one(item: &'a str) -> Self {
|
||||
pub fn one(item: &'a str) -> Self {
|
||||
if item.contains(',') {
|
||||
//panic!("tried creating name list with comma in item: {item}");
|
||||
}
|
||||
Self(item)
|
||||
}
|
||||
pub(crate) fn none() -> NameList<'static> {
|
||||
pub fn none() -> NameList<'static> {
|
||||
NameList("")
|
||||
}
|
||||
pub(crate) fn iter(&self) -> std::str::Split<char> {
|
||||
pub fn iter(&self) -> std::str::Split<char> {
|
||||
self.0.split(',')
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue