Read messages off the wire from git clients
Diff
Cargo.lock | 2 ++
Cargo.toml | 2 ++
src/main.rs | 27 ++++++++++++++++++++++++++-
src/git/codec.rs | 68 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
src/git/mod.rs | 2 ++
5 files changed, 95 insertions(+), 6 deletions(-)
@@ -189,11 +189,13 @@
"anyhow",
"async-trait",
"axum",
"bytes",
"env_logger",
"futures",
"thrussh",
"thrussh-keys",
"tokio",
"tokio-util",
"tower",
"tower-http",
]
@@ -17,3 +17,5 @@
thrussh-keys = "0.21"
anyhow = "1"
env_logger = "0.9"
tokio-util = { version = "0.6", features = ["codec"] }
bytes = "1"
@@ -7,6 +7,9 @@
use thrussh::server::{Auth, Session};
use thrussh::*;
use thrussh_keys::*;
use bytes::BytesMut;
use crate::git::codec::GitCodec;
use tokio_util::codec::Decoder;
#[tokio::main]
async fn main() {
@@ -26,13 +29,20 @@
struct Server;
impl server::Server for Server {
type Handler = Self;
fn new(&mut self, _: Option<std::net::SocketAddr>) -> Self {
self.clone()
type Handler = Handler;
fn new(&mut self, _: Option<std::net::SocketAddr>) -> Self::Handler {
Handler::default()
}
}
#[derive(Default)]
struct Handler {
codec: GitCodec,
input_bytes: BytesMut,
}
impl server::Handler for Server {
impl server::Handler for Handler {
type Error = anyhow::Error;
type FutureAuth = futures::future::Ready<Result<(Self, server::Auth), anyhow::Error>>;
type FutureUnit = futures::future::Ready<Result<(Self, Session), anyhow::Error>>;
@@ -101,9 +111,14 @@
eprintln!("finished auth pubkey");
self.finished_auth(server::Auth::Accept)
}
fn data(mut self, _channel: ChannelId, data: &[u8], session: Session) -> Self::FutureUnit {
self.input_bytes.extend_from_slice(data);
while let Some(frame) = self.codec.decode(&mut self.input_bytes).unwrap() {
eprintln!("data: {:x?}", frame);
}
fn data(self, _channel: ChannelId, data: &[u8], session: Session) -> Self::FutureUnit {
eprintln!("got data: {:x?}", data);
futures::future::ready(Ok((self, session)))
}
@@ -1,0 +1,68 @@
use tokio_util::codec;
use bytes::{Bytes, Buf};
#[derive(Default)]
pub struct GitCodec;
impl codec::Decoder for GitCodec {
type Item = Bytes;
type Error = anyhow::Error;
fn decode(&mut self, src: &mut bytes::BytesMut) -> Result<Option<Self::Item>, Self::Error> {
if src.len() < 4 {
return Ok(None);
}
let mut length_bytes = [0u8; 4];
length_bytes.copy_from_slice(&src[..4]);
let length = u16::from_str_radix(std::str::from_utf8(&length_bytes)?, 16)? as usize;
if length == 0
|| length == 1
|| length == 2
{
src.advance(4);
return self.decode(src);
}
if length > 65520 || length <= 4 {
return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "protocol abuse").into());
}
if src.len() < length {
src.reserve(length - src.len());
return Ok(None);
}
let mut bytes = src.split_to(length);
bytes.advance(4);
if bytes.ends_with(b"\n") {
bytes.truncate(bytes.len() - 1);
}
Ok(Some(bytes.freeze()))
}
}
#[cfg(test)]
mod test {
use tokio_util::codec::Decoder;
use bytes::BytesMut;
use std::fmt::Write;
#[test]
fn decode() {
let mut codec = super::GitCodec;
let mut bytes = BytesMut::new();
bytes.write_str("0015agent=git/2.32.0").unwrap();
let res = codec.decode(&mut bytes).unwrap();
assert_eq!(res, None);
bytes.write_char('\n').unwrap();
let res = codec.decode(&mut bytes).unwrap();
assert_eq!(res.as_deref(), Some("agent=git/2.32.0".as_bytes()));
}
}
@@ -1,3 +1,5 @@
pub mod codec;
use thrussh::CryptoVec;
pub const END_OF_MESSAGE: &'static [u8] = b"0000";