mirror of
https://github.com/Noratrieb/cluelessh.git
synced 2026-01-14 16:35:06 +01:00
refactor
This commit is contained in:
parent
ae5db1642c
commit
0efd08dd5c
12 changed files with 268 additions and 155 deletions
19
Cargo.lock
generated
19
Cargo.lock
generated
|
|
@ -225,7 +225,7 @@ version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"eyre",
|
"eyre",
|
||||||
"hex-literal",
|
"hex-literal",
|
||||||
"ssh-transport",
|
"ssh-protocol",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tracing",
|
"tracing",
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
|
|
@ -663,6 +663,23 @@ dependencies = [
|
||||||
"der",
|
"der",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ssh-connection"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"ssh-transport",
|
||||||
|
"tracing",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ssh-protocol"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"ssh-connection",
|
||||||
|
"ssh-transport",
|
||||||
|
"tracing",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ssh-transport"
|
name = "ssh-transport"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
[workspace]
|
[workspace]
|
||||||
members = [ "ssh-transport"]
|
members = ["ssh-connection", "ssh-protocol", "ssh-transport"]
|
||||||
|
|
||||||
[package]
|
[package]
|
||||||
name = "fakessh"
|
name = "fakessh"
|
||||||
|
|
@ -9,7 +9,7 @@ edition = "2021"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
eyre = "0.6.12"
|
eyre = "0.6.12"
|
||||||
hex-literal = "0.4.1"
|
hex-literal = "0.4.1"
|
||||||
ssh-transport = { path = "./ssh-transport" }
|
ssh-protocol = { path = "./ssh-protocol" }
|
||||||
|
|
||||||
tokio = { version = "1.39.2", features = ["full"] }
|
tokio = { version = "1.39.2", features = ["full"] }
|
||||||
tracing = "0.1.40"
|
tracing = "0.1.40"
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,10 @@ use tokio::{
|
||||||
};
|
};
|
||||||
use tracing::{error, info};
|
use tracing::{error, info};
|
||||||
|
|
||||||
use ssh_transport::{ServerConnection, SshStatus, ThreadRngRand};
|
use ssh_protocol::{
|
||||||
|
transport::{self, ThreadRngRand},
|
||||||
|
ServerConnection, SshStatus,
|
||||||
|
};
|
||||||
use tracing_subscriber::EnvFilter;
|
use tracing_subscriber::EnvFilter;
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
|
|
@ -53,7 +56,7 @@ async fn handle_connection(next: (TcpStream, SocketAddr)) -> Result<()> {
|
||||||
// }
|
// }
|
||||||
//}
|
//}
|
||||||
|
|
||||||
let mut state = ServerConnection::new(ThreadRngRand);
|
let mut state = ServerConnection::new(transport::ServerConnection::new(ThreadRngRand));
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let mut buf = [0; 1024];
|
let mut buf = [0; 1024];
|
||||||
|
|
@ -80,8 +83,6 @@ async fn handle_connection(next: (TcpStream, SocketAddr)) -> Result<()> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
while let Some(channel_update) = state.next_channel_update() {}
|
|
||||||
|
|
||||||
while let Some(msg) = state.next_msg_to_send() {
|
while let Some(msg) = state.next_msg_to_send() {
|
||||||
conn.write_all(&msg.to_bytes())
|
conn.write_all(&msg.to_bytes())
|
||||||
.await
|
.await
|
||||||
|
|
|
||||||
8
ssh-connection/Cargo.toml
Normal file
8
ssh-connection/Cargo.toml
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
[package]
|
||||||
|
name = "ssh-connection"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
ssh-transport = { path = "../ssh-transport" }
|
||||||
|
tracing = "0.1.40"
|
||||||
5
ssh-connection/README.md
Normal file
5
ssh-connection/README.md
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
# ssh-connection
|
||||||
|
|
||||||
|
Connection layer for SSH. This crate takes care of channel multiplexing.
|
||||||
|
|
||||||
|
Based on [RFC 4254 The Secure Shell (SSH) Connection Protocol](https://datatracker.ietf.org/doc/html/rfc4254).
|
||||||
|
|
@ -1,12 +1,11 @@
|
||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
use tracing::{debug, warn};
|
use tracing::{debug, warn};
|
||||||
|
|
||||||
use crate::client_error;
|
use ssh_transport::client_error;
|
||||||
use crate::packet::Packet;
|
use ssh_transport::packet::Packet;
|
||||||
use crate::parse::{Parser, Writer};
|
use ssh_transport::Result;
|
||||||
use crate::Result;
|
|
||||||
|
|
||||||
pub(crate) struct ServerChannelsState {
|
pub struct ServerChannelsState {
|
||||||
packets_to_send: VecDeque<Packet>,
|
packets_to_send: VecDeque<Packet>,
|
||||||
channels: Vec<SessionChannel>,
|
channels: Vec<SessionChannel>,
|
||||||
|
|
||||||
|
|
@ -26,13 +25,17 @@ pub struct ChannelUpdate {
|
||||||
pub channel: u32,
|
pub channel: u32,
|
||||||
pub kind: ChannelUpdateKind,
|
pub kind: ChannelUpdateKind,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum ChannelUpdateKind {
|
pub enum ChannelUpdateKind {
|
||||||
ChannelData(Vec<u8>),
|
Create { kind: String, args: Vec<u8> },
|
||||||
|
Request { kind: String, args: Vec<u8> },
|
||||||
|
Data { data: Vec<u8> },
|
||||||
|
ExtendedData { code: u32, data: Vec<u8> },
|
||||||
|
Eof,
|
||||||
|
ChannelClosed,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ServerChannelsState {
|
impl ServerChannelsState {
|
||||||
pub(crate) fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
ServerChannelsState {
|
ServerChannelsState {
|
||||||
packets_to_send: VecDeque::new(),
|
packets_to_send: VecDeque::new(),
|
||||||
channels: Vec::new(),
|
channels: Vec::new(),
|
||||||
|
|
@ -40,14 +43,24 @@ impl ServerChannelsState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn on_packet(&mut self, packet_type: u8, mut payload: Parser<'_>) -> Result<()> {
|
pub fn recv_packet(&mut self, packet: Packet) -> Result<()> {
|
||||||
|
let mut packet = packet.payload_parser();
|
||||||
|
let packet_type = packet.u8()?;
|
||||||
match packet_type {
|
match packet_type {
|
||||||
|
Packet::SSH_MSG_GLOBAL_REQUEST => {
|
||||||
|
let request_name = packet.utf8_string()?;
|
||||||
|
let want_reply = packet.bool()?;
|
||||||
|
debug!(?request_name, ?want_reply, "Received global request");
|
||||||
|
|
||||||
|
self.packets_to_send
|
||||||
|
.push_back(Packet::new_msg_request_failure());
|
||||||
|
}
|
||||||
Packet::SSH_MSG_CHANNEL_OPEN => {
|
Packet::SSH_MSG_CHANNEL_OPEN => {
|
||||||
// <https://datatracker.ietf.org/doc/html/rfc4254#section-5.1>
|
// <https://datatracker.ietf.org/doc/html/rfc4254#section-5.1>
|
||||||
let channel_type = payload.utf8_string()?;
|
let channel_type = packet.utf8_string()?;
|
||||||
let sender_channel = payload.u32()?;
|
let sender_channel = packet.u32()?;
|
||||||
let initial_window_size = payload.u32()?;
|
let initial_window_size = packet.u32()?;
|
||||||
let max_packet_size = payload.u32()?;
|
let max_packet_size = packet.u32()?;
|
||||||
|
|
||||||
debug!(?channel_type, ?sender_channel, "Opening channel");
|
debug!(?channel_type, ?sender_channel, "Opening channel");
|
||||||
|
|
||||||
|
|
@ -85,8 +98,8 @@ impl ServerChannelsState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Packet::SSH_MSG_CHANNEL_DATA => {
|
Packet::SSH_MSG_CHANNEL_DATA => {
|
||||||
let our_channel = payload.u32()?;
|
let our_channel = packet.u32()?;
|
||||||
let data = payload.string()?;
|
let data = packet.string()?;
|
||||||
|
|
||||||
let channel = self.channel(our_channel)?;
|
let channel = self.channel(our_channel)?;
|
||||||
channel.recv_bytes(data);
|
channel.recv_bytes(data);
|
||||||
|
|
@ -108,7 +121,7 @@ impl ServerChannelsState {
|
||||||
}
|
}
|
||||||
Packet::SSH_MSG_CHANNEL_CLOSE => {
|
Packet::SSH_MSG_CHANNEL_CLOSE => {
|
||||||
// <https://datatracker.ietf.org/doc/html/rfc4254#section-5.3>
|
// <https://datatracker.ietf.org/doc/html/rfc4254#section-5.3>
|
||||||
let our_channel = payload.u32()?;
|
let our_channel = packet.u32()?;
|
||||||
let channel = self.channel(our_channel)?;
|
let channel = self.channel(our_channel)?;
|
||||||
if !channel.we_closed {
|
if !channel.we_closed {
|
||||||
let close = Packet::new_msg_channel_close(channel.peer_channel);
|
let close = Packet::new_msg_channel_close(channel.peer_channel);
|
||||||
|
|
@ -120,9 +133,9 @@ impl ServerChannelsState {
|
||||||
debug!("Channel has been closed");
|
debug!("Channel has been closed");
|
||||||
}
|
}
|
||||||
Packet::SSH_MSG_CHANNEL_REQUEST => {
|
Packet::SSH_MSG_CHANNEL_REQUEST => {
|
||||||
let our_channel = payload.u32()?;
|
let our_channel = packet.u32()?;
|
||||||
let request_type = payload.utf8_string()?;
|
let request_type = packet.utf8_string()?;
|
||||||
let want_reply = payload.bool()?;
|
let want_reply = packet.bool()?;
|
||||||
|
|
||||||
debug!(?our_channel, ?request_type, "Got channel request");
|
debug!(?our_channel, ?request_type, "Got channel request");
|
||||||
|
|
||||||
|
|
@ -131,12 +144,12 @@ impl ServerChannelsState {
|
||||||
|
|
||||||
match request_type {
|
match request_type {
|
||||||
"pty-req" => {
|
"pty-req" => {
|
||||||
let term = payload.utf8_string()?;
|
let term = packet.utf8_string()?;
|
||||||
let width_chars = payload.u32()?;
|
let width_chars = packet.u32()?;
|
||||||
let height_rows = payload.u32()?;
|
let height_rows = packet.u32()?;
|
||||||
let _width_px = payload.u32()?;
|
let _width_px = packet.u32()?;
|
||||||
let _height_px = payload.u32()?;
|
let _height_px = packet.u32()?;
|
||||||
let _term_modes = payload.string()?;
|
let _term_modes = packet.string()?;
|
||||||
|
|
||||||
debug!(
|
debug!(
|
||||||
?our_channel,
|
?our_channel,
|
||||||
|
|
@ -186,7 +199,7 @@ impl ServerChannelsState {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn packets_to_send(&mut self) -> impl Iterator<Item = Packet> + '_ {
|
pub fn packets_to_send(&mut self) -> impl Iterator<Item = Packet> + '_ {
|
||||||
self.packets_to_send.drain(..)
|
self.packets_to_send.drain(..)
|
||||||
}
|
}
|
||||||
|
|
||||||
9
ssh-protocol/Cargo.toml
Normal file
9
ssh-protocol/Cargo.toml
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
[package]
|
||||||
|
name = "ssh-protocol"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
ssh-connection = { path = "../ssh-connection" }
|
||||||
|
ssh-transport = { path = "../ssh-transport" }
|
||||||
|
tracing = "0.1.40"
|
||||||
5
ssh-protocol/README.md
Normal file
5
ssh-protocol/README.md
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
# ssh-protocol
|
||||||
|
|
||||||
|
Combines `ssh-connection` and `ssh-transport` into a higher level interface.
|
||||||
|
|
||||||
|
Also implements authentication based on [RFC 4252 The Secure Shell (SSH) Authentication Protocol](https://datatracker.ietf.org/doc/html/rfc4252).
|
||||||
160
ssh-protocol/src/lib.rs
Normal file
160
ssh-protocol/src/lib.rs
Normal file
|
|
@ -0,0 +1,160 @@
|
||||||
|
pub use ssh_transport as transport;
|
||||||
|
pub use ssh_transport::{Result, SshStatus};
|
||||||
|
|
||||||
|
pub struct ServerConnection {
|
||||||
|
transport: ssh_transport::ServerConnection,
|
||||||
|
state: ServerConnectionState,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum ServerConnectionState {
|
||||||
|
Auth(auth::BadAuth),
|
||||||
|
Open(ssh_connection::ServerChannelsState),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ServerConnection {
|
||||||
|
pub fn new(transport: ssh_transport::ServerConnection) -> Self {
|
||||||
|
Self {
|
||||||
|
transport,
|
||||||
|
state: ServerConnectionState::Auth(auth::BadAuth::new()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn recv_bytes(&mut self, bytes: &[u8]) -> Result<()> {
|
||||||
|
self.transport.recv_bytes(bytes)?;
|
||||||
|
|
||||||
|
while let Some(packet) = self.transport.next_plaintext_packet() {
|
||||||
|
match &mut self.state {
|
||||||
|
ServerConnectionState::Auth(auth) => {
|
||||||
|
auth.recv_packet(packet)?;
|
||||||
|
for to_send in auth.packets_to_send() {
|
||||||
|
self.transport.send_plaintext_packet(to_send);
|
||||||
|
}
|
||||||
|
if auth.is_authenticated() {
|
||||||
|
self.state = ServerConnectionState::Open(ssh_connection::ServerChannelsState::new());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ServerConnectionState::Open(con) => {
|
||||||
|
con.recv_packet(packet)?;
|
||||||
|
for to_send in con.packets_to_send() {
|
||||||
|
self.transport.send_plaintext_packet(to_send);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn next_msg_to_send(&mut self) -> Option<ssh_transport::Msg> {
|
||||||
|
self.transport.next_msg_to_send()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <https://datatracker.ietf.org/doc/html/rfc4252>
|
||||||
|
pub mod auth {
|
||||||
|
use std::collections::VecDeque;
|
||||||
|
|
||||||
|
use ssh_transport::{client_error, packet::Packet, parse::NameList, Result};
|
||||||
|
use tracing::info;
|
||||||
|
|
||||||
|
pub struct BadAuth {
|
||||||
|
has_failed: bool,
|
||||||
|
packets_to_send: VecDeque<Packet>,
|
||||||
|
is_authenticated: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BadAuth {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
has_failed: false,
|
||||||
|
packets_to_send: VecDeque::new(),
|
||||||
|
is_authenticated: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn recv_packet(&mut self, packet: Packet) -> Result<()> {
|
||||||
|
assert!(!self.is_authenticated, "Must not feed more packets to authentication after authentication is been completed, check with .is_authenticated()");
|
||||||
|
|
||||||
|
// 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.queue_packet(Packet::new_msg_userauth_success());
|
||||||
|
|
||||||
|
self.is_authenticated = true;
|
||||||
|
}
|
||||||
|
"publickey" => {
|
||||||
|
info!("Got public key");
|
||||||
|
// Don't worry queen, your key is correct!
|
||||||
|
self.queue_packet(Packet::new_msg_userauth_success());
|
||||||
|
self.is_authenticated = true;
|
||||||
|
}
|
||||||
|
_ if self.has_failed => {
|
||||||
|
return Err(client_error!(
|
||||||
|
"client tried unsupported method twice: {method_name}"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
// Initial.
|
||||||
|
|
||||||
|
self.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.queue_packet(Packet::new_msg_userauth_failure(
|
||||||
|
NameList::one("publickey"),
|
||||||
|
false,
|
||||||
|
));
|
||||||
|
// Stay in the same state
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn packets_to_send(&mut self) -> impl Iterator<Item = Packet> + '_ {
|
||||||
|
self.packets_to_send.drain(..)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_authenticated(&self) -> bool {
|
||||||
|
self.is_authenticated
|
||||||
|
}
|
||||||
|
|
||||||
|
fn queue_packet(&mut self, packet: Packet) {
|
||||||
|
self.packets_to_send.push_back(packet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -6,10 +6,6 @@ Based on [RFC 4253 The Secure Shell (SSH) Transport Layer Protocol](https://data
|
||||||
and [RFC 4251 The Secure Shell (SSH) Protocol Architecture](https://datatracker.ietf.org/doc/html/rfc4251)
|
and [RFC 4251 The Secure Shell (SSH) Protocol Architecture](https://datatracker.ietf.org/doc/html/rfc4251)
|
||||||
and [RFC 4250 The Secure Shell (SSH) Protocol Assigned Numbers](https://datatracker.ietf.org/doc/html/rfc4250).
|
and [RFC 4250 The Secure Shell (SSH) Protocol Assigned Numbers](https://datatracker.ietf.org/doc/html/rfc4250).
|
||||||
|
|
||||||
Also authentication and connection for now.
|
|
||||||
- [The Secure Shell (SSH) Authentication Protocol](https://datatracker.ietf.org/doc/html/rfc4252)
|
|
||||||
- [The Secure Shell (SSH) Connection Protocol](https://datatracker.ietf.org/doc/html/rfc4254)
|
|
||||||
|
|
||||||
Other relevant RFCs:
|
Other relevant RFCs:
|
||||||
- [RFC 5649 AES Galois Counter Mode for the Secure Shell Transport Layer Protocol](https://datatracker.ietf.org/doc/html/rfc5647)
|
- [RFC 5649 AES Galois Counter Mode for the Secure Shell Transport Layer Protocol](https://datatracker.ietf.org/doc/html/rfc5647)
|
||||||
- [RFC 5656 Elliptic Curve Algorithm Integration in the Secure Shell Transport Layer](https://datatracker.ietf.org/doc/html/rfc5656)
|
- [RFC 5656 Elliptic Curve Algorithm Integration in the Secure Shell Transport Layer](https://datatracker.ietf.org/doc/html/rfc5656)
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
mod channel;
|
|
||||||
mod keys;
|
mod keys;
|
||||||
pub mod packet;
|
pub mod packet;
|
||||||
pub mod parse;
|
pub mod parse;
|
||||||
|
|
@ -6,7 +5,6 @@ pub mod parse;
|
||||||
use core::str;
|
use core::str;
|
||||||
use std::{collections::VecDeque, mem::take};
|
use std::{collections::VecDeque, mem::take};
|
||||||
|
|
||||||
use channel::ServerChannelsState;
|
|
||||||
use ed25519_dalek::ed25519::signature::Signer;
|
use ed25519_dalek::ed25519::signature::Signer;
|
||||||
use packet::{
|
use packet::{
|
||||||
DhKeyExchangeInitPacket, DhKeyExchangeInitReplyPacket, KeyExchangeInitPacket, Packet,
|
DhKeyExchangeInitPacket, DhKeyExchangeInitReplyPacket, KeyExchangeInitPacket, Packet,
|
||||||
|
|
@ -18,7 +16,6 @@ use sha2::Digest;
|
||||||
use tracing::{debug, info, trace};
|
use tracing::{debug, info, trace};
|
||||||
use x25519_dalek::{EphemeralSecret, PublicKey};
|
use x25519_dalek::{EphemeralSecret, PublicKey};
|
||||||
|
|
||||||
pub use channel::ChannelUpdate;
|
|
||||||
pub use packet::Msg;
|
pub use packet::Msg;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
|
@ -52,7 +49,7 @@ pub struct ServerConnection {
|
||||||
packet_transport: PacketTransport,
|
packet_transport: PacketTransport,
|
||||||
rng: Box<dyn SshRng + Send + Sync>,
|
rng: Box<dyn SshRng + Send + Sync>,
|
||||||
|
|
||||||
channel_updates: VecDeque<ChannelUpdate>,
|
plaintext_packets: VecDeque<Packet>,
|
||||||
}
|
}
|
||||||
|
|
||||||
enum ServerState {
|
enum ServerState {
|
||||||
|
|
@ -72,14 +69,7 @@ enum ServerState {
|
||||||
k: [u8; 32],
|
k: [u8; 32],
|
||||||
},
|
},
|
||||||
ServiceRequest,
|
ServiceRequest,
|
||||||
// At this point we transfer to <https://datatracker.ietf.org/doc/html/rfc4252>
|
Open,
|
||||||
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),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait SshRng {
|
pub trait SshRng {
|
||||||
|
|
@ -121,7 +111,8 @@ impl ServerConnection {
|
||||||
},
|
},
|
||||||
packet_transport: PacketTransport::new(),
|
packet_transport: PacketTransport::new(),
|
||||||
rng: Box::new(rng),
|
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);
|
self.packet_transport.set_key(h, k);
|
||||||
}
|
}
|
||||||
ServerState::ServiceRequest => {
|
ServerState::ServiceRequest => {
|
||||||
|
// TODO: this should probably move out of here? unsure.
|
||||||
if packet.payload.first() != Some(&Packet::SSH_MSG_SERVICE_REQUEST) {
|
if packet.payload.first() != Some(&Packet::SSH_MSG_SERVICE_REQUEST) {
|
||||||
return Err(client_error!("did not send SSH_MSG_SERVICE_REQUEST"));
|
return Err(client_error!("did not send SSH_MSG_SERVICE_REQUEST"));
|
||||||
}
|
}
|
||||||
|
|
@ -362,107 +354,10 @@ impl ServerConnection {
|
||||||
writer.finish()
|
writer.finish()
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
self.state = ServerState::UserAuthRequest { has_failed: false };
|
self.state = ServerState::Open;
|
||||||
}
|
}
|
||||||
ServerState::UserAuthRequest { has_failed } => {
|
ServerState::Open => {
|
||||||
// This is a super simplistic implementation of RFC4252 SSH authentication.
|
self.plaintext_packets.push_back(packet);
|
||||||
// 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}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -473,8 +368,12 @@ impl ServerConnection {
|
||||||
self.packet_transport.next_msg_to_send()
|
self.packet_transport.next_msg_to_send()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn next_channel_update(&mut self) -> Option<ChannelUpdate> {
|
pub fn next_plaintext_packet(&mut self) -> Option<Packet> {
|
||||||
self.channel_updates.pop_front()
|
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);
|
pub struct NameList<'a>(&'a str);
|
||||||
|
|
||||||
impl<'a> NameList<'a> {
|
impl<'a> NameList<'a> {
|
||||||
pub(crate) fn one(item: &'a str) -> Self {
|
pub fn one(item: &'a str) -> Self {
|
||||||
if item.contains(',') {
|
if item.contains(',') {
|
||||||
//panic!("tried creating name list with comma in item: {item}");
|
//panic!("tried creating name list with comma in item: {item}");
|
||||||
}
|
}
|
||||||
Self(item)
|
Self(item)
|
||||||
}
|
}
|
||||||
pub(crate) fn none() -> NameList<'static> {
|
pub fn none() -> NameList<'static> {
|
||||||
NameList("")
|
NameList("")
|
||||||
}
|
}
|
||||||
pub(crate) fn iter(&self) -> std::str::Split<char> {
|
pub fn iter(&self) -> std::str::Split<char> {
|
||||||
self.0.split(',')
|
self.0.split(',')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue