Compare commits
2 Commits
0e48e930ba
...
a45e57803a
Author | SHA1 | Date |
---|---|---|
Michael Pfaff | a45e57803a | |
Michael Pfaff | cc71f5bb9a |
File diff suppressed because it is too large
Load Diff
|
@ -1,3 +1,11 @@
|
||||||
|
# Quinoa
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- PAM authentication
|
||||||
|
- SSH key authentication (reads from ~/.ssh/authorized_keys)
|
||||||
|
- Currently this method will skip other PAM methods like 2FA
|
||||||
|
|
||||||
## Resources
|
## Resources
|
||||||
|
|
||||||
Some resources used in the development of the program.
|
Some resources used in the development of the program.
|
||||||
|
|
|
@ -180,7 +180,8 @@ pub async fn client_authenticate(
|
||||||
}
|
}
|
||||||
Question::Prompt {
|
Question::Prompt {
|
||||||
prompt,
|
prompt,
|
||||||
echo,
|
// TODO: implement this
|
||||||
|
echo: _,
|
||||||
} => {
|
} => {
|
||||||
stdout.write_all(prompt.to_bytes()).await?;
|
stdout.write_all(prompt.to_bytes()).await?;
|
||||||
stdout.write_all(b" ").await?;
|
stdout.write_all(b" ").await?;
|
||||||
|
|
|
@ -41,7 +41,8 @@ async fn server_authorize_key(user_id: Uid, public_key: &ssh_key::public::Public
|
||||||
let mut authorized_keys = String::with_capacity(home.len() + PATH.len());
|
let mut authorized_keys = String::with_capacity(home.len() + PATH.len());
|
||||||
authorized_keys.push_str(home);
|
authorized_keys.push_str(home);
|
||||||
authorized_keys.push_str(PATH);
|
authorized_keys.push_str(PATH);
|
||||||
let authorized_keys = tokio::fs::read_to_string(authorized_keys).await?;
|
match tokio::fs::read_to_string(&authorized_keys).await {
|
||||||
|
Ok(authorized_keys) => {
|
||||||
let mut authorized_keys = ssh_key::authorized_keys::AuthorizedKeys::new(&authorized_keys);
|
let mut authorized_keys = ssh_key::authorized_keys::AuthorizedKeys::new(&authorized_keys);
|
||||||
|
|
||||||
while let Some(r) = authorized_keys.next() {
|
while let Some(r) = authorized_keys.next() {
|
||||||
|
@ -50,6 +51,12 @@ async fn server_authorize_key(user_id: Uid, public_key: &ssh_key::public::Public
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {}
|
||||||
|
Err(e) => {
|
||||||
|
error!("unable to read authorized keys file at {:?}: {}", authorized_keys, e);
|
||||||
|
}
|
||||||
|
};
|
||||||
Err(anyhow!("Key provided by the client is not authorized to connect"))
|
Err(anyhow!("Key provided by the client is not authorized to connect"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
96
src/main.rs
96
src/main.rs
|
@ -26,9 +26,7 @@ use std::future::Future;
|
||||||
use std::mem::ManuallyDrop;
|
use std::mem::ManuallyDrop;
|
||||||
use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4};
|
use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4};
|
||||||
use std::os::fd::FromRawFd;
|
use std::os::fd::FromRawFd;
|
||||||
use std::os::unix::process::CommandExt;
|
|
||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
use std::process::Stdio;
|
|
||||||
use std::ptr::NonNull;
|
use std::ptr::NonNull;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
@ -40,7 +38,6 @@ use nix::unistd::Uid;
|
||||||
use quinn::{ReadExactError, RecvStream, SendStream};
|
use quinn::{ReadExactError, RecvStream, SendStream};
|
||||||
use rustls::client::ServerCertVerifier;
|
use rustls::client::ServerCertVerifier;
|
||||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||||
use tokio::process::Command;
|
|
||||||
use tracing::Instrument;
|
use tracing::Instrument;
|
||||||
use user_info::UserInfo;
|
use user_info::UserInfo;
|
||||||
use webpki::SubjectNameRef;
|
use webpki::SubjectNameRef;
|
||||||
|
@ -319,14 +316,6 @@ async fn run_server() -> Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_broken_pipe<T>(r: &Result<T, std::io::Error>) -> bool {
|
|
||||||
if let Err(e) = r {
|
|
||||||
e.kind() == std::io::ErrorKind::BrokenPipe
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn transport_config() -> quinn::TransportConfig {
|
fn transport_config() -> quinn::TransportConfig {
|
||||||
let mut transport = quinn::TransportConfig::default();
|
let mut transport = quinn::TransportConfig::default();
|
||||||
transport.stream_receive_window((64u32 * 1024 * 1024).into());
|
transport.stream_receive_window((64u32 * 1024 * 1024).into());
|
||||||
|
@ -545,7 +534,7 @@ impl ServerCertVerifier for InformedServerCertVerifier {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let cert = webpki::EndEntityCert::try_from(end_entity.0.as_ref()).map_err(pki_error)?;
|
let _cert = webpki::EndEntityCert::try_from(end_entity.0.as_ref()).map_err(pki_error)?;
|
||||||
|
|
||||||
let ip_addr_slot;
|
let ip_addr_slot;
|
||||||
let subject_name = match server_name {
|
let subject_name = match server_name {
|
||||||
|
@ -785,8 +774,7 @@ async fn run_client(mut args: std::env::Args) -> Result<()> {
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.and_then(|s| s.split_once('@'))
|
.and_then(|s| s.split_once('@'))
|
||||||
.context("Expected an argument of the form USERNAME@HOST")?;
|
.context("Expected an argument of the form USERNAME@HOST")?;
|
||||||
let (host_name, port) = host.split_once(':').unwrap_or((host, "8022"));
|
let (host_name, _port) = host.split_once(':').unwrap_or((host, "8022"));
|
||||||
let port = port.parse::<u16>()?;
|
|
||||||
|
|
||||||
let mut client_crypto = rustls::ClientConfig::builder()
|
let mut client_crypto = rustls::ClientConfig::builder()
|
||||||
.with_safe_defaults()
|
.with_safe_defaults()
|
||||||
|
@ -935,7 +923,7 @@ async fn do_forward_to(conn: &quinn::Connection, spec: ForwardSpec) -> Result<()
|
||||||
socket.bind(spec.bind.into())?;
|
socket.bind(spec.bind.into())?;
|
||||||
let list = socket.listen(1024)?;
|
let list = socket.listen(1024)?;
|
||||||
loop {
|
loop {
|
||||||
let (mut local_stream, peer) = list.accept().await?;
|
let (mut local_stream, _peer) = list.accept().await?;
|
||||||
let (send, recv) = open_stream(
|
let (send, recv) = open_stream(
|
||||||
conn,
|
conn,
|
||||||
&Stream::Forward {
|
&Stream::Forward {
|
||||||
|
@ -1083,7 +1071,7 @@ async fn greet_conn(cfg: &'static ServerConfig, conn: quinn::Connecting) -> Resu
|
||||||
|
|
||||||
#[cfg(feature = "server")]
|
#[cfg(feature = "server")]
|
||||||
async fn authenticate_conn(
|
async fn authenticate_conn(
|
||||||
cfg: &'static ServerConfig,
|
_cfg: &'static ServerConfig,
|
||||||
conn: &quinn::Connection,
|
conn: &quinn::Connection,
|
||||||
) -> Result<(UserInfo, Vec<(CString, CString)>)> {
|
) -> Result<(UserInfo, Vec<(CString, CString)>)> {
|
||||||
info!("authenticating connection");
|
info!("authenticating connection");
|
||||||
|
@ -1192,78 +1180,16 @@ async fn handle_conn(
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_stream_exec(
|
async fn handle_stream_exec(
|
||||||
cfg: &ServerConfig,
|
_cfg: &ServerConfig,
|
||||||
mut send: SendStream,
|
_send: SendStream,
|
||||||
mut recv: RecvStream,
|
_recv: RecvStream,
|
||||||
user_info: &UserInfo,
|
_user_info: &UserInfo,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
(|| todo!())();
|
todo!()
|
||||||
let mut cmd = std::process::Command::new("");
|
|
||||||
cmd.stdout(Stdio::piped())
|
|
||||||
.stderr(Stdio::piped())
|
|
||||||
.stdin(Stdio::piped());
|
|
||||||
#[cfg(target_family = "unix")]
|
|
||||||
cmd.process_group(0);
|
|
||||||
info!("Running {:?}", cmd);
|
|
||||||
let mut sh = Command::from(cmd).kill_on_drop(true).spawn()?;
|
|
||||||
|
|
||||||
let mut stdout = sh.stdout.take().unwrap();
|
|
||||||
let mut stderr = sh.stderr.take().unwrap();
|
|
||||||
let mut stdin = sh.stdin.take().unwrap();
|
|
||||||
let mut stdout_buf = Vec::with_capacity(4096);
|
|
||||||
let mut stderr_buf = Vec::with_capacity(4096);
|
|
||||||
let mut stdin_buf = Vec::with_capacity(4096);
|
|
||||||
|
|
||||||
let mut stdout_eof = false;
|
|
||||||
let mut stderr_eof = false;
|
|
||||||
|
|
||||||
loop {
|
|
||||||
tokio::select! {
|
|
||||||
r = sh.wait() => {
|
|
||||||
let code = r?;
|
|
||||||
send.finish().await?;
|
|
||||||
if !code.success() {
|
|
||||||
info!("Child exit: {}", code);
|
|
||||||
|
|
||||||
recv.stop(1u8.into())?;
|
|
||||||
return Ok(());
|
|
||||||
} else {
|
|
||||||
info!("Child exit");
|
|
||||||
recv.stop(0u8.into())?;
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
r = stdout.read_buf(&mut stdout_buf), if !stdout_eof => {
|
|
||||||
if is_broken_pipe(&r) || r? == 0 {
|
|
||||||
stdout_eof = true;
|
|
||||||
info!("stdout eof");
|
|
||||||
} else {
|
|
||||||
send.write_all(&stdout_buf).await?;
|
|
||||||
info!("sent stdout: {:x?}", stdout_buf);
|
|
||||||
stdout_buf.clear();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
r = stderr.read_buf(&mut stderr_buf), if !stderr_eof => {
|
|
||||||
if is_broken_pipe(&r) || r? == 0 {
|
|
||||||
stderr_eof = true;
|
|
||||||
info!("stderr eof");
|
|
||||||
} else {
|
|
||||||
send.write_all(&stderr_buf).await?;
|
|
||||||
stderr_buf.clear();
|
|
||||||
info!("sent stderr: {:x?}", stderr_buf);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
r = recv.read_buf(&mut stdin_buf) => if r? > 0 {
|
|
||||||
stdin.write_all(&stdin_buf).await?;
|
|
||||||
stdin_buf.clear();
|
|
||||||
info!("recv stdin");
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_stream_shell(
|
async fn handle_stream_shell(
|
||||||
cfg: &ServerConfig,
|
_cfg: &ServerConfig,
|
||||||
mut send: SendStream,
|
mut send: SendStream,
|
||||||
mut recv: RecvStream,
|
mut recv: RecvStream,
|
||||||
user_info: &UserInfo,
|
user_info: &UserInfo,
|
||||||
|
@ -1636,7 +1562,7 @@ async fn handle_stream_shell(
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_stream_forward(
|
async fn handle_stream_forward(
|
||||||
cfg: &ServerConfig,
|
_cfg: &ServerConfig,
|
||||||
send: SendStream,
|
send: SendStream,
|
||||||
recv: RecvStream,
|
recv: RecvStream,
|
||||||
addr: Ipv4Addr,
|
addr: Ipv4Addr,
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
//! Forked from <https://github.com/gifnksm/etc-passwd>.
|
||||||
|
//!
|
||||||
//! Get user information stored in the password file `/etc/passwd`.
|
//! Get user information stored in the password file `/etc/passwd`.
|
||||||
//!
|
//!
|
||||||
//! This crate provides a safe wrapper for libc functions such as [`getpwnam_r(3)`] and [`getpwuid_r(3)`].
|
//! This crate provides a safe wrapper for libc functions such as [`getpwnam_r(3)`] and [`getpwuid_r(3)`].
|
||||||
|
|
Loading…
Reference in New Issue