Login into a CLI with std::process::Command

I was trying to automate a login that is pretty boring to do with the "normal" flow.
The cli asks for, in order:

    • username
    • password
    • a key that is generated on my phone

My idea is pass the username and password programatically and forward the 3 step to the user (myself) to fill it.

What I've tried so far

Spawning:

let mut child = Command::new("openvpn3")
        .stdin(Stdio::piped())
        .args([
            "session-start",
            "--config",
            "/home/user/path-to-config.ovpn",
        ])
        .spawn()?;

    let mut stdin = child.stdin.take().unwrap();

    stdin.write_all(b"username")?;
    stdin.write_all(b"password")?;
    
    child.wait()?.success();

but with this implementation always are asked to do the 1, 2 and 3 steps.

I've tried to read the output of the child.stdout, but this never proceeds to forward the 3 step to be filled.

I've read the docs too, and read about the Child process that is generated by the Command and Im not sure what a child process is.

Thank you guys for any tip, trick and help :slight_smile:

When you execute stdin.write_all(b"username"), all it will do is write the bytes b"username" to some buffer your kernel has that is eventually connected to the child process's stdin. If you want to make sure the child process to receive those bytes, you will also need to do a stdin.flush()? to "flush" any buffers between your code and the child process.

Another thing you might need to do is send a newline character after each write. At the moment, all the child process is seeing is b"usernamepassword" and it's probably still waiting for you to finish entering the username.

let mut child = Command::new("openvpn3")
        .stdin(Stdio::piped())
        .args([
            "session-start",
            "--config",
            "/home/user/path-to-config.ovpn",
        ])
        .spawn()?;

    let mut stdin = child.stdin.take().unwrap();

    // the writeln!() macro is like println!(), except it'll write
    // to some writer object instead of stdout. By using
    // writeln!() instead of write!(), we'll automatically send
    // a newline character with each write.
    writeln!(stdin, "username")?;
    writeln!(stdin, "username")?;
    // Make sure any buffered bytes are sent through to the child process
    stdin.flush()?;
    
    child.wait()?.success();

The way operating systems keep track of processes is to build a big tree structure and on startup, your kernel will spawn a single program which is responsible for bringing the rest of the system up.

Whenever one program starts another program, that new program is attached as a child node on the process tree (hence the phrase, "child process") and the kernel plumbs things up so you can talk to each other's stdin/stdout/stderr (the mechanism is called a "pipe").

The std::process::Child object gives you access to the parent's side of these pipes, which is why a write to child.stdin or read from child.stdout will let you write/read the child process's stdin/stdout.

3 Likes

Another issue is that you piped stdin of the child to your own process, so it is no longer connected to the terminal. As such your program is now responsible for asking for the key and forwarding to the child process rather than having the terminal input go directly to the child process.

1 Like

Thanks for the really helpfull explanation and how I have to pass the buffer to the process... I'd imagined too use a '\n', but didnt use :man_shrugging:

I didnt pay attention on flush, if I'd spent more time in the docs maybe I could manage to reach the solution by myself. thak you for that!

Now I have an issue with the child.wait()?.success() that appears that never receive the 3 step input

This is likely because of what I said in my previous message.

Oh, sorry. I didnt see your answer.

The problem that you mentioned is that I piped the stdin of the child process to my own process (which is the program itself, right?) and I need a way to pass the buffer of my process stdin pipe to the child process?

The easiest thing is probably to read_line from your program standard input and then writeln! it to the child's input, as you did before with login and password.

1 Like

Worked like a charm! thank you!