mirror of
https://github.com/Noratrieb/cluelessh.git
synced 2026-01-16 01:15:04 +01:00
stuff
This commit is contained in:
parent
8114b5a195
commit
d41e474f33
9 changed files with 174 additions and 71 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
|
@ -438,6 +438,7 @@ dependencies = [
|
||||||
"tokio",
|
"tokio",
|
||||||
"tracing",
|
"tracing",
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
|
"users",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
|
||||||
|
|
@ -43,10 +43,11 @@ async fn main() -> eyre::Result<()> {
|
||||||
info!(password = %auth.password, "Got password");
|
info!(password = %auth.password, "Got password");
|
||||||
|
|
||||||
// Don't worry queen, your password is correct!
|
// Don't worry queen, your password is correct!
|
||||||
Ok(())
|
Ok(true)
|
||||||
})
|
})
|
||||||
})),
|
})),
|
||||||
verify_pubkey: None,
|
check_pubkey: None,
|
||||||
|
verify_signature: None,
|
||||||
auth_banner: Some(
|
auth_banner: Some(
|
||||||
"\
|
"\
|
||||||
!! this system ONLY allows catgirls to enter !!\r\n\
|
!! this system ONLY allows catgirls to enter !!\r\n\
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ tracing.workspace = true
|
||||||
eyre.workspace = true
|
eyre.workspace = true
|
||||||
tracing-subscriber = { version = "0.3.18", features = ["env-filter", "json"] }
|
tracing-subscriber = { version = "0.3.18", features = ["env-filter", "json"] }
|
||||||
rustix = { version = "0.38.34", features = ["pty", "termios", "procfs", "process", "stdio"] }
|
rustix = { version = "0.38.34", features = ["pty", "termios", "procfs", "process", "stdio"] }
|
||||||
|
users = "0.11.0"
|
||||||
|
|
||||||
[lints]
|
[lints]
|
||||||
workspace = true
|
workspace = true
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ mod pty;
|
||||||
use std::{io, net::SocketAddr, process::ExitStatus, sync::Arc};
|
use std::{io, net::SocketAddr, process::ExitStatus, sync::Arc};
|
||||||
|
|
||||||
use cluelessh_tokio::{server::ServerAuthVerify, Channel};
|
use cluelessh_tokio::{server::ServerAuthVerify, Channel};
|
||||||
use eyre::{bail, Context, Result};
|
use eyre::{bail, Context, OptionExt, Result};
|
||||||
use pty::Pty;
|
use pty::Pty;
|
||||||
use rustix::termios::Winsize;
|
use rustix::termios::Winsize;
|
||||||
use tokio::{
|
use tokio::{
|
||||||
|
|
@ -18,6 +18,7 @@ use cluelessh_protocol::{
|
||||||
ChannelUpdateKind, SshStatus,
|
ChannelUpdateKind, SshStatus,
|
||||||
};
|
};
|
||||||
use tracing_subscriber::EnvFilter;
|
use tracing_subscriber::EnvFilter;
|
||||||
|
use users::os::unix::UserExt;
|
||||||
|
|
||||||
#[tokio::main(flavor = "current_thread")]
|
#[tokio::main(flavor = "current_thread")]
|
||||||
async fn main() -> eyre::Result<()> {
|
async fn main() -> eyre::Result<()> {
|
||||||
|
|
@ -36,16 +37,22 @@ async fn main() -> eyre::Result<()> {
|
||||||
let listener = TcpListener::bind(addr).await.wrap_err("binding listener")?;
|
let listener = TcpListener::bind(addr).await.wrap_err("binding listener")?;
|
||||||
|
|
||||||
let auth_verify = ServerAuthVerify {
|
let auth_verify = ServerAuthVerify {
|
||||||
verify_password: Some(Arc::new(|auth| {
|
verify_password: None,
|
||||||
|
verify_signature: Some(Arc::new(|auth| {
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
debug!(user = %auth.user, "Attempting password login");
|
debug!(user = %auth.user, "Attempting publickey login");
|
||||||
// Don't worry queen, your password is correct!
|
|
||||||
warn!("Letting in unauthenticated user");
|
warn!("Letting in unauthenticated user");
|
||||||
Ok(())
|
Ok(true)
|
||||||
})
|
})
|
||||||
})),
|
})),
|
||||||
verify_pubkey: None,
|
check_pubkey: Some(Arc::new(|auth| {
|
||||||
auth_banner: Some("welcome to my server!!!\r\ni hope you enjoy our stay.\r\n".to_owned()),
|
Box::pin(async move {
|
||||||
|
debug!(user = %auth.user, "Attempting publickey check");
|
||||||
|
warn!("Letting in unauthenticated user");
|
||||||
|
Ok(true)
|
||||||
|
})
|
||||||
|
})),
|
||||||
|
auth_banner: Some("welcome to my server!!!\r\ni hope you enjoy your stay.\r\n".to_owned()),
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut listener = cluelessh_tokio::server::ServerListener::new(listener, auth_verify);
|
let mut listener = cluelessh_tokio::server::ServerListener::new(listener, auth_verify);
|
||||||
|
|
@ -95,9 +102,10 @@ async fn handle_connection(
|
||||||
}
|
}
|
||||||
|
|
||||||
while let Some(channel) = conn.next_new_channel() {
|
while let Some(channel) = conn.next_new_channel() {
|
||||||
|
let user = conn.inner().authenticated_user().unwrap().to_owned();
|
||||||
if *channel.kind() == ChannelKind::Session {
|
if *channel.kind() == ChannelKind::Session {
|
||||||
tokio::spawn(async {
|
tokio::spawn(async move {
|
||||||
let _ = handle_session_channel(channel).await;
|
let _ = handle_session_channel(user, channel).await;
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
warn!("Trying to open non-session channel");
|
warn!("Trying to open non-session channel");
|
||||||
|
|
@ -107,16 +115,18 @@ async fn handle_connection(
|
||||||
}
|
}
|
||||||
|
|
||||||
struct SessionState {
|
struct SessionState {
|
||||||
|
user: String,
|
||||||
pty: Option<Pty>,
|
pty: Option<Pty>,
|
||||||
channel: Channel,
|
channel: Channel,
|
||||||
process_exit_send: mpsc::Sender<Result<ExitStatus, io::Error>>,
|
process_exit_send: mpsc::Sender<Result<ExitStatus, io::Error>>,
|
||||||
process_exit_recv: mpsc::Receiver<Result<ExitStatus, io::Error>>,
|
process_exit_recv: mpsc::Receiver<Result<ExitStatus, io::Error>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_session_channel(channel: Channel) -> Result<()> {
|
async fn handle_session_channel(user: String, channel: Channel) -> Result<()> {
|
||||||
let (process_exit_send, process_exit_recv) = tokio::sync::mpsc::channel(1);
|
let (process_exit_send, process_exit_recv) = tokio::sync::mpsc::channel(1);
|
||||||
|
|
||||||
let mut state = SessionState {
|
let mut state = SessionState {
|
||||||
|
user,
|
||||||
pty: None,
|
pty: None,
|
||||||
channel,
|
channel,
|
||||||
process_exit_send,
|
process_exit_send,
|
||||||
|
|
@ -200,7 +210,13 @@ impl SessionState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ChannelRequest::Shell { want_reply } => {
|
ChannelRequest::Shell { want_reply } => {
|
||||||
let shell = "bash";
|
let user = self.user.clone();
|
||||||
|
let user =
|
||||||
|
tokio::task::spawn_blocking(move || users::get_user_by_name(&user))
|
||||||
|
.await?
|
||||||
|
.ok_or_eyre("failed to find user")?;
|
||||||
|
|
||||||
|
let shell = user.shell();
|
||||||
|
|
||||||
let mut cmd = Command::new(shell);
|
let mut cmd = Command::new(shell);
|
||||||
cmd.env_clear();
|
cmd.env_clear();
|
||||||
|
|
@ -210,8 +226,8 @@ impl SessionState {
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: **user** home directory
|
// TODO: **user** home directory
|
||||||
cmd.current_dir(std::env::var("HOME").unwrap_or_else(|_| "/".to_owned()));
|
cmd.current_dir(user.home_dir());
|
||||||
cmd.env("USER", "nora");
|
cmd.env("USER", user.name());
|
||||||
|
|
||||||
let mut shell = cmd.spawn()?;
|
let mut shell = cmd.spawn()?;
|
||||||
let process_exit_send = self.process_exit_send.clone();
|
let process_exit_send = self.process_exit_send.clone();
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@
|
||||||
use std::{
|
use std::{
|
||||||
io::{Read, Write},
|
io::{Read, Write},
|
||||||
os::fd::{AsRawFd, BorrowedFd, OwnedFd},
|
os::fd::{AsRawFd, BorrowedFd, OwnedFd},
|
||||||
path::PathBuf,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use eyre::{Context, Result};
|
use eyre::{Context, Result};
|
||||||
|
|
@ -24,6 +23,7 @@ pub struct Pty {
|
||||||
pub ctrl_write_send: mpsc::Sender<Vec<u8>>,
|
pub ctrl_write_send: mpsc::Sender<Vec<u8>>,
|
||||||
pub ctrl_read_recv: mpsc::Receiver<Vec<u8>>,
|
pub ctrl_read_recv: mpsc::Receiver<Vec<u8>>,
|
||||||
user_pty: OwnedFd,
|
user_pty: OwnedFd,
|
||||||
|
user_pty_name: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Pty {
|
impl Pty {
|
||||||
|
|
@ -37,12 +37,12 @@ impl Pty {
|
||||||
rustix::pty::unlockpt(&controller).wrap_err("unlocking pty")?;
|
rustix::pty::unlockpt(&controller).wrap_err("unlocking pty")?;
|
||||||
|
|
||||||
let user_pty_name = rustix::pty::ptsname(&controller, Vec::new())?;
|
let user_pty_name = rustix::pty::ptsname(&controller, Vec::new())?;
|
||||||
let user_pty_name =
|
let user_pty_name = std::str::from_utf8(user_pty_name.as_bytes())
|
||||||
std::str::from_utf8(user_pty_name.as_bytes()).wrap_err("pty name is invalid UTF-8")?;
|
.wrap_err("pty name is invalid UTF-8")?
|
||||||
let user_pty_name = PathBuf::from(user_pty_name);
|
.to_owned();
|
||||||
|
|
||||||
let user_pty =
|
let user_pty =
|
||||||
rustix::fs::open(user_pty_name, OFlags::RDWR | OFlags::NOCTTY, Mode::empty())?;
|
rustix::fs::open(&user_pty_name, OFlags::RDWR | OFlags::NOCTTY, Mode::empty())?;
|
||||||
|
|
||||||
// Configure terminal:
|
// Configure terminal:
|
||||||
rustix::termios::tcsetwinsize(&user_pty, winsize)?;
|
rustix::termios::tcsetwinsize(&user_pty, winsize)?;
|
||||||
|
|
@ -85,6 +85,7 @@ impl Pty {
|
||||||
ctrl_write_send,
|
ctrl_write_send,
|
||||||
ctrl_read_recv,
|
ctrl_read_recv,
|
||||||
user_pty,
|
user_pty,
|
||||||
|
user_pty_name,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -106,6 +107,7 @@ impl Pty {
|
||||||
Ok(())
|
Ok(())
|
||||||
});
|
});
|
||||||
cmd.env("TERM", &self.term);
|
cmd.env("TERM", &self.term);
|
||||||
|
cmd.env("SSH_TTY", &self.user_pty_name);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ pub struct ServerConnection {
|
||||||
enum ServerConnectionState {
|
enum ServerConnectionState {
|
||||||
Setup(HashSet<AuthOption>, Option<String>),
|
Setup(HashSet<AuthOption>, Option<String>),
|
||||||
Auth(auth::ServerAuth),
|
Auth(auth::ServerAuth),
|
||||||
Open(cluelessh_connection::ChannelsState),
|
Open(cluelessh_connection::ChannelsState, String),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ServerConnection {
|
impl ServerConnection {
|
||||||
|
|
@ -63,7 +63,7 @@ impl ServerConnection {
|
||||||
self.transport.send_plaintext_packet(to_send);
|
self.transport.send_plaintext_packet(to_send);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ServerConnectionState::Open(con) => {
|
ServerConnectionState::Open(con, _) => {
|
||||||
con.recv_packet(packet)?;
|
con.recv_packet(packet)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -81,7 +81,7 @@ impl ServerConnection {
|
||||||
pub fn next_channel_update(&mut self) -> Option<cluelessh_connection::ChannelUpdate> {
|
pub fn next_channel_update(&mut self) -> Option<cluelessh_connection::ChannelUpdate> {
|
||||||
match &mut self.state {
|
match &mut self.state {
|
||||||
ServerConnectionState::Setup(..) | ServerConnectionState::Auth(_) => None,
|
ServerConnectionState::Setup(..) | ServerConnectionState::Auth(_) => None,
|
||||||
ServerConnectionState::Open(con) => con.next_channel_update(),
|
ServerConnectionState::Open(con, _) => con.next_channel_update(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -90,7 +90,7 @@ impl ServerConnection {
|
||||||
ServerConnectionState::Setup(..) | ServerConnectionState::Auth(_) => {
|
ServerConnectionState::Setup(..) | ServerConnectionState::Auth(_) => {
|
||||||
panic!("tried to get connection before it is ready")
|
panic!("tried to get connection before it is ready")
|
||||||
}
|
}
|
||||||
ServerConnectionState::Open(con) => {
|
ServerConnectionState::Open(con, _) => {
|
||||||
con.do_operation(op);
|
con.do_operation(op);
|
||||||
self.progress();
|
self.progress();
|
||||||
}
|
}
|
||||||
|
|
@ -104,12 +104,14 @@ impl ServerConnection {
|
||||||
for to_send in auth.packets_to_send() {
|
for to_send in auth.packets_to_send() {
|
||||||
self.transport.send_plaintext_packet(to_send);
|
self.transport.send_plaintext_packet(to_send);
|
||||||
}
|
}
|
||||||
if auth.is_authenticated() {
|
if let Some(user) = auth.authenticated_user() {
|
||||||
self.state =
|
self.state = ServerConnectionState::Open(
|
||||||
ServerConnectionState::Open(cluelessh_connection::ChannelsState::new(true));
|
cluelessh_connection::ChannelsState::new(true),
|
||||||
|
user.to_owned(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ServerConnectionState::Open(con) => {
|
ServerConnectionState::Open(con, _) => {
|
||||||
for to_send in con.packets_to_send() {
|
for to_send in con.packets_to_send() {
|
||||||
self.transport.send_plaintext_packet(to_send);
|
self.transport.send_plaintext_packet(to_send);
|
||||||
}
|
}
|
||||||
|
|
@ -119,7 +121,7 @@ impl ServerConnection {
|
||||||
|
|
||||||
pub fn channels(&mut self) -> Option<&mut cluelessh_connection::ChannelsState> {
|
pub fn channels(&mut self) -> Option<&mut cluelessh_connection::ChannelsState> {
|
||||||
match &mut self.state {
|
match &mut self.state {
|
||||||
ServerConnectionState::Open(channels) => Some(channels),
|
ServerConnectionState::Open(channels, _) => Some(channels),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -130,6 +132,13 @@ impl ServerConnection {
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn authenticated_user(&self) -> Option<&str> {
|
||||||
|
match &self.state {
|
||||||
|
ServerConnectionState::Open(_, user) => Some(user),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ClientConnection {
|
pub struct ClientConnection {
|
||||||
|
|
@ -263,7 +272,7 @@ pub mod auth {
|
||||||
pub struct ServerAuth {
|
pub struct ServerAuth {
|
||||||
has_failed: bool,
|
has_failed: bool,
|
||||||
packets_to_send: VecDeque<Packet>,
|
packets_to_send: VecDeque<Packet>,
|
||||||
is_authenticated: bool,
|
is_authenticated: Option<String>,
|
||||||
options: HashSet<AuthOption>,
|
options: HashSet<AuthOption>,
|
||||||
banner: Option<String>,
|
banner: Option<String>,
|
||||||
server_requests: VecDeque<ServerRequest>,
|
server_requests: VecDeque<ServerRequest>,
|
||||||
|
|
@ -272,14 +281,28 @@ pub mod auth {
|
||||||
|
|
||||||
pub enum ServerRequest {
|
pub enum ServerRequest {
|
||||||
VerifyPassword(VerifyPassword),
|
VerifyPassword(VerifyPassword),
|
||||||
VerifyPubkey(VerifyPubkey),
|
/// Check whether a pubkey is usable.
|
||||||
|
CheckPubkey(CheckPubkey),
|
||||||
|
/// Verify the signature from a pubkey.
|
||||||
|
VerifySignature(VerifySignature),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
pub struct VerifyPassword {
|
pub struct VerifyPassword {
|
||||||
pub user: String,
|
pub user: String,
|
||||||
pub password: String,
|
pub password: String,
|
||||||
}
|
}
|
||||||
pub struct VerifyPubkey {
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct CheckPubkey {
|
||||||
|
pub user: String,
|
||||||
|
pub session_identifier: [u8; 32],
|
||||||
|
pub pubkey_alg_name: Vec<u8>,
|
||||||
|
pub pubkey: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct VerifySignature {
|
||||||
pub user: String,
|
pub user: String,
|
||||||
pub session_identifier: [u8; 32],
|
pub session_identifier: [u8; 32],
|
||||||
pub pubkey_alg_name: Vec<u8>,
|
pub pubkey_alg_name: Vec<u8>,
|
||||||
|
|
@ -303,7 +326,7 @@ pub mod auth {
|
||||||
has_failed: false,
|
has_failed: false,
|
||||||
packets_to_send: VecDeque::new(),
|
packets_to_send: VecDeque::new(),
|
||||||
options,
|
options,
|
||||||
is_authenticated: false,
|
is_authenticated: None,
|
||||||
session_ident,
|
session_ident,
|
||||||
banner,
|
banner,
|
||||||
server_requests: VecDeque::new(),
|
server_requests: VecDeque::new(),
|
||||||
|
|
@ -311,7 +334,7 @@ pub mod auth {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn recv_packet(&mut self, packet: Packet) -> Result<()> {
|
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()");
|
assert!(self.is_authenticated.is_none(), "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.
|
// This is a super simplistic implementation of RFC4252 SSH authentication.
|
||||||
// We ask for a public key, and always let that one pass.
|
// We ask for a public key, and always let that one pass.
|
||||||
|
|
@ -366,24 +389,31 @@ pub mod auth {
|
||||||
self.send_failure();
|
self.send_failure();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Whether the client is just checking whether the public key is allowed.
|
let has_signature = p.bool()?;
|
||||||
let is_check = p.bool()?;
|
|
||||||
if is_check {
|
|
||||||
todo!();
|
|
||||||
}
|
|
||||||
|
|
||||||
let pubkey_alg_name = p.string()?;
|
let pubkey_alg_name = p.string()?;
|
||||||
let public_key_blob = p.string()?;
|
let public_key_blob = p.string()?;
|
||||||
let signature = p.string()?;
|
|
||||||
|
|
||||||
self.server_requests
|
// Whether the client is just checking whether the public key is allowed.
|
||||||
.push_back(ServerRequest::VerifyPubkey(VerifyPubkey {
|
if !has_signature {
|
||||||
user: username.to_owned(),
|
self.server_requests
|
||||||
session_identifier: self.session_ident,
|
.push_back(ServerRequest::CheckPubkey(CheckPubkey {
|
||||||
pubkey_alg_name: pubkey_alg_name.to_vec(),
|
user: username.to_owned(),
|
||||||
pubkey: public_key_blob.to_vec(),
|
session_identifier: self.session_ident,
|
||||||
signature: signature.to_vec(),
|
pubkey_alg_name: pubkey_alg_name.to_vec(),
|
||||||
}));
|
pubkey: public_key_blob.to_vec(),
|
||||||
|
}));
|
||||||
|
} else {
|
||||||
|
let signature = p.string()?;
|
||||||
|
self.server_requests
|
||||||
|
.push_back(ServerRequest::VerifySignature(VerifySignature {
|
||||||
|
user: username.to_owned(),
|
||||||
|
session_identifier: self.session_ident,
|
||||||
|
pubkey_alg_name: pubkey_alg_name.to_vec(),
|
||||||
|
pubkey: public_key_blob.to_vec(),
|
||||||
|
signature: signature.to_vec(),
|
||||||
|
}));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
_ if self.has_failed => {
|
_ if self.has_failed => {
|
||||||
return Err(peer_error!(
|
return Err(peer_error!(
|
||||||
|
|
@ -402,10 +432,20 @@ pub mod auth {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn verification_result(&mut self, is_ok: bool) {
|
pub fn pubkey_check_result(&mut self, is_ok: bool, alg: &[u8], key_blob: &[u8]) {
|
||||||
|
if is_ok {
|
||||||
|
self.queue_packet(Packet::new_msg_userauth_pk_ok(alg, key_blob));
|
||||||
|
} else {
|
||||||
|
self.send_failure();
|
||||||
|
// It's ok, don't treat this as a fatal failure.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: improve types with a newtype around an authenticated user
|
||||||
|
pub fn verification_result(&mut self, is_ok: bool, user: String) {
|
||||||
if is_ok {
|
if is_ok {
|
||||||
self.queue_packet(Packet::new_msg_userauth_success());
|
self.queue_packet(Packet::new_msg_userauth_success());
|
||||||
self.is_authenticated = true;
|
self.is_authenticated = Some(user);
|
||||||
} else {
|
} else {
|
||||||
self.send_failure();
|
self.send_failure();
|
||||||
self.has_failed = true;
|
self.has_failed = true;
|
||||||
|
|
@ -416,8 +456,8 @@ pub mod auth {
|
||||||
self.packets_to_send.drain(..)
|
self.packets_to_send.drain(..)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_authenticated(&self) -> bool {
|
pub fn authenticated_user(&self) -> Option<&str> {
|
||||||
self.is_authenticated
|
self.is_authenticated.as_deref()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn server_requests(&mut self) -> impl Iterator<Item = ServerRequest> + '_ {
|
pub fn server_requests(&mut self) -> impl Iterator<Item = ServerRequest> + '_ {
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ use tokio::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use cluelessh_protocol::{
|
use cluelessh_protocol::{
|
||||||
auth::{AuthOption, VerifyPassword, VerifyPubkey},
|
auth::{AuthOption, CheckPubkey, VerifyPassword, VerifySignature},
|
||||||
ChannelUpdateKind, SshStatus,
|
ChannelUpdateKind, SshStatus,
|
||||||
};
|
};
|
||||||
use eyre::{eyre, ContextCompat, OptionExt, Result, WrapErr};
|
use eyre::{eyre, ContextCompat, OptionExt, Result, WrapErr};
|
||||||
|
|
@ -49,16 +49,18 @@ pub struct ServerConnection<S> {
|
||||||
}
|
}
|
||||||
|
|
||||||
enum Operation {
|
enum Operation {
|
||||||
VerifyPassword(Result<()>),
|
VerifyPassword(String, Result<bool>),
|
||||||
VerifyPubkey(Result<()>),
|
CheckPubkey(Result<bool>, Vec<u8>, Vec<u8>),
|
||||||
|
VerifySignature(String, Result<bool>),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub type AuthFn<A, R> = Arc<dyn Fn(A) -> BoxFuture<'static, R> + Send + Sync>;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct ServerAuthVerify {
|
pub struct ServerAuthVerify {
|
||||||
pub verify_password:
|
pub verify_password: Option<AuthFn<VerifyPassword, Result<bool>>>,
|
||||||
Option<Arc<dyn Fn(VerifyPassword) -> BoxFuture<'static, Result<()>> + Send + Sync>>,
|
pub verify_signature: Option<AuthFn<VerifySignature, Result<bool>>>,
|
||||||
pub verify_pubkey:
|
pub check_pubkey: Option<AuthFn<CheckPubkey, Result<bool>>>,
|
||||||
Option<Arc<dyn Fn(VerifyPubkey) -> BoxFuture<'static, Result<()>> + Send + Sync>>,
|
|
||||||
pub auth_banner: Option<String>,
|
pub auth_banner: Option<String>,
|
||||||
}
|
}
|
||||||
fn _assert_send_sync() {
|
fn _assert_send_sync() {
|
||||||
|
|
@ -104,13 +106,18 @@ impl<S: AsyncRead + AsyncWrite> ServerConnection<S> {
|
||||||
if auth_verify.verify_password.is_some() {
|
if auth_verify.verify_password.is_some() {
|
||||||
options.insert(AuthOption::Password);
|
options.insert(AuthOption::Password);
|
||||||
}
|
}
|
||||||
if auth_verify.verify_pubkey.is_some() {
|
if auth_verify.verify_signature.is_some() {
|
||||||
options.insert(AuthOption::PublicKey);
|
options.insert(AuthOption::PublicKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
if options.is_empty() {
|
if options.is_empty() {
|
||||||
panic!("no auth options provided");
|
panic!("no auth options provided");
|
||||||
}
|
}
|
||||||
|
assert_eq!(
|
||||||
|
auth_verify.check_pubkey.is_some(),
|
||||||
|
auth_verify.verify_signature.is_some(),
|
||||||
|
"Public key auth only partially supported"
|
||||||
|
);
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
stream: Box::pin(stream),
|
stream: Box::pin(stream),
|
||||||
|
|
@ -151,20 +158,42 @@ impl<S: AsyncRead + AsyncWrite> ServerConnection<S> {
|
||||||
.clone()
|
.clone()
|
||||||
.ok_or_eyre("password auth not supported")?;
|
.ok_or_eyre("password auth not supported")?;
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
let result = verify(password_verify).await;
|
let result = verify(password_verify.clone()).await;
|
||||||
let _ = send.send(Operation::VerifyPassword(result)).await;
|
let _ = send
|
||||||
|
.send(Operation::VerifyPassword(password_verify.user, result))
|
||||||
|
.await;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
cluelessh_protocol::auth::ServerRequest::VerifyPubkey(pubkey_verify) => {
|
cluelessh_protocol::auth::ServerRequest::CheckPubkey(check_pubkey) => {
|
||||||
let send = self.operations_send.clone();
|
let send = self.operations_send.clone();
|
||||||
let verify = self
|
let check = self
|
||||||
.auth_verify
|
.auth_verify
|
||||||
.verify_pubkey
|
.check_pubkey
|
||||||
.clone()
|
.clone()
|
||||||
.ok_or_eyre("pubkey auth not supported")?;
|
.ok_or_eyre("pubkey auth not supported")?;
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
let result = verify(pubkey_verify).await;
|
let result = check(check_pubkey.clone()).await;
|
||||||
let _ = send.send(Operation::VerifyPubkey(result)).await;
|
let _ = send
|
||||||
|
.send(Operation::CheckPubkey(
|
||||||
|
result,
|
||||||
|
check_pubkey.pubkey_alg_name,
|
||||||
|
check_pubkey.pubkey,
|
||||||
|
))
|
||||||
|
.await;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
cluelessh_protocol::auth::ServerRequest::VerifySignature(pubkey_verify) => {
|
||||||
|
let send = self.operations_send.clone();
|
||||||
|
let verify = self
|
||||||
|
.auth_verify
|
||||||
|
.verify_signature
|
||||||
|
.clone()
|
||||||
|
.ok_or_eyre("pubkey auth not supported")?;
|
||||||
|
tokio::spawn(async move {
|
||||||
|
let result = verify(pubkey_verify.clone()).await;
|
||||||
|
let _ = send
|
||||||
|
.send(Operation::VerifySignature(pubkey_verify.user, result))
|
||||||
|
.await;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -279,11 +308,14 @@ impl<S: AsyncRead + AsyncWrite> ServerConnection<S> {
|
||||||
}
|
}
|
||||||
op = self.operations_recv.recv() => {
|
op = self.operations_recv.recv() => {
|
||||||
match op {
|
match op {
|
||||||
Some(Operation::VerifyPubkey(result)) => if let Some(auth) = self.proto.auth() {
|
Some(Operation::VerifySignature(user, result)) => if let Some(auth) = self.proto.auth() {
|
||||||
auth.verification_result(result.is_ok());
|
auth.verification_result(result?, user);
|
||||||
},
|
},
|
||||||
Some(Operation::VerifyPassword(result)) => if let Some(auth) = self.proto.auth() {
|
Some(Operation::CheckPubkey(result, alg, key_blob)) => if let Some(auth) = self.proto.auth() {
|
||||||
auth.verification_result(result.is_ok());
|
auth.pubkey_check_result(result?, &alg, &key_blob);
|
||||||
|
},
|
||||||
|
Some(Operation::VerifyPassword(user, result)) => if let Some(auth) = self.proto.auth() {
|
||||||
|
auth.verification_result(result?, user);
|
||||||
},
|
},
|
||||||
None => {}
|
None => {}
|
||||||
}
|
}
|
||||||
|
|
@ -336,4 +368,8 @@ impl<S: AsyncRead + AsyncWrite> ServerConnection<S> {
|
||||||
pub fn next_new_channel(&mut self) -> Option<Channel> {
|
pub fn next_new_channel(&mut self) -> Option<Channel> {
|
||||||
self.new_channels.pop_front()
|
self.new_channels.pop_front()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn inner(&self) -> &cluelessh_protocol::ServerConnection {
|
||||||
|
&self.proto
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ pub const SSH_MSG_USERAUTH_SUCCESS: u8 = 52;
|
||||||
pub const SSH_MSG_USERAUTH_BANNER: u8 = 53;
|
pub const SSH_MSG_USERAUTH_BANNER: u8 = 53;
|
||||||
|
|
||||||
// 60 to 79 User authentication method specific (numbers can be reused for different authentication methods)
|
// 60 to 79 User authentication method specific (numbers can be reused for different authentication methods)
|
||||||
|
pub const SSH_MSG_USERAUTH_PK_OK: u8 = 60;
|
||||||
|
|
||||||
// -----
|
// -----
|
||||||
// Connection protocol:
|
// Connection protocol:
|
||||||
|
|
@ -70,6 +71,7 @@ pub fn packet_type_to_string(packet_type: u8) -> &'static str {
|
||||||
51 => "SSH_MSG_USERAUTH_FAILURE",
|
51 => "SSH_MSG_USERAUTH_FAILURE",
|
||||||
52 => "SSH_MSG_USERAUTH_SUCCESS",
|
52 => "SSH_MSG_USERAUTH_SUCCESS",
|
||||||
53 => "SSH_MSG_USERAUTH_BANNER",
|
53 => "SSH_MSG_USERAUTH_BANNER",
|
||||||
|
60 => "SSH_MSG_USERAUTH_PK_OK",
|
||||||
80 => "SSH_MSG_GLOBAL_REQUEST",
|
80 => "SSH_MSG_GLOBAL_REQUEST",
|
||||||
81 => "SSH_MSG_REQUEST_SUCCESS",
|
81 => "SSH_MSG_REQUEST_SUCCESS",
|
||||||
82 => "SSH_MSG_REQUEST_FAILURE",
|
82 => "SSH_MSG_REQUEST_FAILURE",
|
||||||
|
|
|
||||||
|
|
@ -92,6 +92,10 @@ ctors! {
|
||||||
fn new_msg_userauth_banner(SSH_MSG_USERAUTH_BANNER; msg: string, language_tag: string);
|
fn new_msg_userauth_banner(SSH_MSG_USERAUTH_BANNER; msg: string, language_tag: string);
|
||||||
|
|
||||||
// 60 to 79 User authentication method specific (numbers can be reused for different authentication methods)
|
// 60 to 79 User authentication method specific (numbers can be reused for different authentication methods)
|
||||||
|
fn new_msg_userauth_pk_ok(SSH_MSG_USERAUTH_PK_OK;
|
||||||
|
key_alg: string,
|
||||||
|
key_blob: string,
|
||||||
|
);
|
||||||
|
|
||||||
// -----
|
// -----
|
||||||
// Connection protocol:
|
// Connection protocol:
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue