ssl - 在 Rust 中通过 TLS 重定向 stdio

标签 ssl rust file-descriptor stdio

我正在尝试复制 ncat 中的“-e”选项以将 Rust 中的 stdio 重定向到远程 ncat 监听器。
我可以通过使用 dup2 然后在 Rust 中执行“/bin/sh”命令来在 TcpStream 上完成它。但是,我不知道如何通过 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, 0, fd)
  • 这提供了两个连接的双向文件描述符

  • 使用 dup2()在此套接字对的一端,用于重定向进程的 I/O(就像您对未加密的 TCP 流所做的那样)
  • 监视另一端和 TLS 流(例如以 select() 类似的方式),以便
  • 从套接字对接收可用的内容并将其发送到 TLS 流
  • 从 TLS 流中接收可用的内容并将其发送到套接字对。


  • 请注意 select()在 TLS 流上(实际上是其底层文件描述符)有点棘手,因为一些字节可能已经被接收(在其底层文件描述符上)并在内部缓冲区中解密,而尚未被应用程序使用。
    在尝试新的 select() 之前,您必须询问 TSL 流的接收缓冲区是否为空。在上面。
    对这个 watch/recv/send 循环使用异步或线程解决方案可能比依赖 select() 更容易。 - 类似的解决方案。

    编辑,在问题中的版本之后
    由于您现在有一个依赖于三个不同管道的解决方案,您可以忘记关于 socketpair() 的所有内容。 .
    std::io::copy() 的调用在您的示例的每个线程中都是一个简单的循环,它从其第一个参数接收一些字节并将它们发送到第二个参数。
    您的 TlsStream 可能是一个执行所有加密 I/O 操作(发送和接收)的单一结构,因此您将无法提供 &mut引用您的多个线程。
    最好的办法可能是编写自己的循环来尝试检测新的传入字节,然后将它们分派(dispatch)到适当的目的地。
    如前所述,我将使用 select()为了那个原因。
    不幸的是,据我所知,在 Rust 中,我们必须依赖 libc 这样的低级功能。为此(在异步世界中,我可能不知道其他高级解决方案......)。
    为了展示主要思想,我在下面制作了一个(不是那么)最小的示例;它肯定远非完美,所以«小心处理»;^)
    (它依赖于 native-tls libc )
    从 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, &mut fdset) };
            max_fd = std::cmp::max(max_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(),
                    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();
    }
    

    关于ssl - 在 Rust 中通过 TLS 重定向 stdio,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/64531243/

    相关文章:

    php - 自分配证书上的 CodeIgniter-cURL

    rust - 为什么这个借用仍然是 "active"?

    arrays - 如何修改数组的最后一项?

    C: 如何将 stderr 从系统命令重定向到 stdout 或文件?

    c - 打开后重复的文件描述符

    ssl - 使用 Mamp Pro 3.4 设置 SSL - 错误 'Your connection is not private'

    postgresql - 如何配置基于 x509 客户端证书的身份验证以连接到基于 AWS RDS 的 PostgreSQL

    java - 没有服务器响应的 SSL 握手与 java 1.8

    rust - 可以在稳定的编译器上控制 Rust 结构对齐吗?

    hadoop - 几分钟后使用Flume file_roll接收器类型卡住了