This commit is contained in:
nora 2024-08-25 18:42:19 +02:00
parent 8114b5a195
commit d41e474f33
9 changed files with 174 additions and 71 deletions

1
Cargo.lock generated
View file

@ -438,6 +438,7 @@ dependencies = [
"tokio", "tokio",
"tracing", "tracing",
"tracing-subscriber", "tracing-subscriber",
"users",
] ]
[[package]] [[package]]

View file

@ -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\

View file

@ -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

View file

@ -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();

View file

@ -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(())

View file

@ -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> + '_ {

View file

@ -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
}
} }

View file

@ -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",

View file

@ -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: