微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

在Rust中通过TLS重定向stdio

如何解决在Rust中通过TLS重定向stdio

我正在尝试在ncat中复制“ -e”选项,以将Rust中的stdio重定向到远程ncat侦听器。

我可以通过使用dup2在Tcpstream上执行此操作,然后在Rust中执行“ / bin / sh”命令。但是,我不知道如何通过TLS进行操作,因为重定向似乎需要文件描述符,而Tlsstream似乎没有提供文件描述符。

有人可以建议吗?

编辑2020年11月2日

Rust论坛中的某个人已经与我(https://users.rust-lang.org/t/redirect-stdio-pipes-and-file-descriptors/50751/8分享一个解决方案,现在我正在尝试研究如何通过TLS连接重定向stdio

let mut command_output = std::process::Command::new("/bin/sh")
    .stdin(Stdio::piped())
    .stdout(Stdio::piped())
    .stderr(Stdio::piped())
    .spawn()
    .expect("cannot execute command");

let mut command_stdin = command_output.stdin.unwrap();
println!("command_stdin {}",command_stdin.as_raw_fd());

let copy_stdin_thread = std::thread::spawn(move || {
    io::copy(&mut io::stdin(),&mut command_stdin)
});
        
let mut command_stdout = command_output.stdout.unwrap();
println!("command_stdout {}",command_stdout.as_raw_fd());

let copy_stdout_thread = std::thread::spawn(move || {
   io::copy(&mut command_stdout,&mut io::stdout())
});

let command_stderr = command_output.stderr.unwrap();
println!("command_stderr {}",command_stderr.as_raw_fd());

let copy_stderr_thread = std::thread::spawn(move || {
    io::copy(&mut command_stderr,&mut io::stderr())
});

copy_stdin_thread.join().unwrap()?;
copy_stdout_thread.join().unwrap()?;
copy_stderr_thread.join().unwrap()?;

解决方法

这个问题和答案不是特定于Rust的。

您注意到了一个重要的事实,即重定向过程的I / O必须是文件描述符。 您的应用程序中一种可能的解决方案是

  • 使用socketpair(PF_LOCAL,SOCK_STREAM,fd)
    • 这提供了两个相连的双向文件描述符
  • 在此套接字对的一端使用dup2()进行重定向进程的I / O(与未加密的TCP流一样)
  • 观看另一端和TLS流(例如,以类似select()的方式),以便
    • 接收来自套接字对的可用内容,并将其发送到TLS流,
    • 接收来自TLS流的可用内容,并将其发送到套接字对。

请注意,TLS流(实际上是其基础文件描述符)上的select()有点棘手,因为某些字节可能已经被接收(在其基础文件描述符上)并在内部缓冲区中解密,而尚未由应用程序使用。 您必须先询问TSL流的接收缓冲区是否为空,然后再对其尝试新的select()。 对于此watch / recv / send循环,使用异步或线程解决方案可能比依靠类似select()的解决方案容易。


修改版本,在问题中

由于您现在有了一个依赖于三个不同管道的解决方案,因此您可以忘记有关socketpair()的所有信息。

在示例的每个线程中对std::io::copy()的调用是一个简单的循环,该循环从其第一个参数接收一些字节并将它们发送到第二个参数。 您的TlsStream可能是执行所有加密I / O操作(发送和接收)的单一结构,因此您将无法在其上为多线程提供&mut引用。>

最好是编写自己的循环,尝试检测新的传入字节,然后将其分派到适当的目的地。 如前所述,我将为此使用select()。 不幸的是,据我所知,在Rust中,我们必须依靠libc这样的低级功能(在异步世界中可能还有我不知道的其他高级别解决方案...)。 / p>

为了展示主要思想,我在下面提供了一个(并非如此)最少的示例。当然,它远非完美,因此“请小心处理”; ^) (它依赖于native-tlslibc

从openssl访问它可以得到

$ openssl s_client -connect localhost:9876
CONNECTED(00000003)
Can't use SSL_get_servername
...
    Extended master secret: yes
---
hello
/bin/sh: line 1: hello: command not found
df
Filesystem     1K-blocks      Used Available Use% Mounted on
dev              4028936         0   4028936   0% /dev
run              4038472      1168   4037304   1% /run
/dev/sda5       30832548  22074768   7168532  76% /
tmpfs            4038472    234916   3803556   6% /dev/shm
tmpfs               4096         0      4096   0% /sys/fs/cgroup
tmpfs            4038472         4   4038468   1% /tmp
/dev/sda6      338368556 219588980 101568392  69% /home
tmpfs             807692        56    807636   1% /run/user/9223
exit
read:errno=0
fn main() {
    let args: Vec<_> = std::env::args().collect();
    let use_simple = args.len() == 2 && args[1] == "s";

    let mut file = std::fs::File::open("server.pfx").unwrap();
    let mut identity = vec![];
    use std::io::Read;
    file.read_to_end(&mut identity).unwrap();
    let identity =
        native_tls::Identity::from_pkcs12(&identity,"dummy").unwrap();

    let listener = std::net::TcpListener::bind("0.0.0.0:9876").unwrap();
    let acceptor = native_tls::TlsAcceptor::new(identity).unwrap();
    let acceptor = std::sync::Arc::new(acceptor);

    for stream in listener.incoming() {
        match stream {
            Ok(stream) => {
                let acceptor = acceptor.clone();
                std::thread::spawn(move || {
                    let stream = acceptor.accept(stream).unwrap();
                    if use_simple {
                        simple_client(stream);
                    } else {
                        redirect_shell(stream);
                    }
                });
            }
            Err(_) => {
                println!("accept failure");
                break;
            }
        }
    }
}

fn simple_client(mut stream: native_tls::TlsStream<std::net::TcpStream>) {
    let mut buffer = [0_u8; 100];
    let mut count = 0;
    loop {
        use std::io::Read;
        if let Ok(sz_r) = stream.read(&mut buffer) {
            if sz_r == 0 {
                println!("EOF");
                break;
            }
            println!(
                "received <{}>",std::str::from_utf8(&buffer[0..sz_r]).unwrap_or("???")
            );
            let reply = format!("message {} is {} bytes long\n",count,sz_r);
            count += 1;
            use std::io::Write;
            if stream.write_all(reply.as_bytes()).is_err() {
                println!("write failure");
                break;
            }
        } else {
            println!("read failure");
            break;
        }
    }
}

fn redirect_shell(mut stream: native_tls::TlsStream<std::net::TcpStream>) {
    // start child process
    let mut child = std::process::Command::new("/bin/sh")
        .stdin(std::process::Stdio::piped())
        .stdout(std::process::Stdio::piped())
        .stderr(std::process::Stdio::piped())
        .spawn()
        .expect("cannot execute command");
    // access useful I/O and file descriptors
    let stdin = child.stdin.as_mut().unwrap();
    let stdout = child.stdout.as_mut().unwrap();
    let stderr = child.stderr.as_mut().unwrap();
    use std::os::unix::io::AsRawFd;
    let stream_fd = stream.get_ref().as_raw_fd();
    let stdout_fd = stdout.as_raw_fd();
    let stderr_fd = stderr.as_raw_fd();
    // main send/recv loop
    use std::io::{Read,Write};
    let mut buffer = [0_u8; 100];
    loop {
        // no need to wait for new incoming bytes on tcp-stream
        // if some are already decoded in the tls-stream
        let already_buffered = match stream.buffered_read_size() {
            Ok(sz) if sz > 0 => true,_ => false,};
        // prepare file descriptors to be watched for by select()
        let mut fdset =
            unsafe { std::mem::MaybeUninit::uninit().assume_init() };
        let mut max_fd = -1;
        unsafe { libc::FD_ZERO(&mut fdset) };
        unsafe { libc::FD_SET(stdout_fd,&mut fdset) };
        max_fd = std::cmp::max(max_fd,stdout_fd);
        unsafe { libc::FD_SET(stderr_fd,stderr_fd);
        if !already_buffered {
            // see above
            unsafe { libc::FD_SET(stream_fd,&mut fdset) };
            max_fd = std::cmp::max(max_fd,stream_fd);
        }
        // block this thread until something new happens
        // on these file-descriptors (don't wait if some bytes
        // are already decoded in the tls-stream)
        let mut zero_timeout =
            unsafe { std::mem::MaybeUninit::zeroed().assume_init() };
        unsafe {
            libc::select(
                max_fd + 1,&mut fdset,std::ptr::null_mut(),if already_buffered {
                    &mut zero_timeout
                } else {
                    std::ptr::null_mut()
                },)
        };
        // this thread is not blocked any more,// try to handle what happened on the file descriptors
        if unsafe { libc::FD_ISSET(stdout_fd,&mut fdset) } {
            // something new happened on stdout,// try to receive some bytes an send them through the tls-stream
            if let Ok(sz_r) = stdout.read(&mut buffer) {
                if sz_r == 0 {
                    println!("EOF detected on stdout");
                    break;
                }
                if stream.write_all(&buffer[0..sz_r]).is_err() {
                    println!("write failure on tls-stream");
                    break;
                }
            } else {
                println!("read failure on process stdout");
                break;
            }
        }
        if unsafe { libc::FD_ISSET(stderr_fd,&mut fdset) } {
            // something new happened on stderr,// try to receive some bytes an send them through the tls-stream
            if let Ok(sz_r) = stderr.read(&mut buffer) {
                if sz_r == 0 {
                    println!("EOF detected on stderr");
                    break;
                }
                if stream.write_all(&buffer[0..sz_r]).is_err() {
                    println!("write failure on tls-stream");
                    break;
                }
            } else {
                println!("read failure on process stderr");
                break;
            }
        }
        if already_buffered
            || unsafe { libc::FD_ISSET(stream_fd,&mut fdset) }
        {
            // something new happened on the tls-stream
            // (or some bytes were already buffered),// try to receive some bytes an send them on stdin
            if let Ok(sz_r) = stream.read(&mut buffer) {
                if sz_r == 0 {
                    println!("EOF detected on tls-stream");
                    break;
                }
                if stdin.write_all(&buffer[0..sz_r]).is_err() {
                    println!("write failure on stdin");
                    break;
                }
            } else {
                println!("read failure on tls-stream");
                break;
            }
        }
    }
    let _ = child.wait();
}

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。