1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
use libssh_rs::*;
use std::io::Read;

fn verify_known_hosts(sess: &Session) -> SshResult<()> {
    let key = sess
        .get_server_public_key()?
        .get_public_key_hash_hexa(PublicKeyHashType::Sha256)?;

    match sess.is_known_server()? {
        KnownHosts::Ok => Ok(()),
        KnownHosts::NotFound | KnownHosts::Unknown => {
            eprintln!("The server is not a known host. Do you trust the host key?");
            eprintln!("Public key hash: {}", key);

            let input = prompt_stdin("Enter yes to trust the key: ")?;
            if input == "yes" {
                sess.update_known_hosts_file()
            } else {
                Err(Error::Fatal("untrusted server".to_string()))
            }
        }
        KnownHosts::Changed => {
            eprintln!("The key for the server has changed. It is now:");
            eprintln!("{}", key);
            Err(Error::Fatal("host key changed".to_string()))
        }
        KnownHosts::Other => {
            eprintln!("The host key for this server was not found, but another");
            eprintln!("type of key exists. An attacker might change the default");
            eprintln!("server key to confuse your client into thinking the key");
            eprintln!("does not exist");
            Err(Error::Fatal("host key has wrong type".to_string()))
        }
    }
}

fn prompt(prompt: &str, echo: bool) -> SshResult<String> {
    get_input(prompt, None, echo, false).ok_or_else(|| Error::Fatal("reading password".to_string()))
}

fn prompt_stdin(prompt: &str) -> SshResult<String> {
    eprintln!("{}", prompt);
    let mut input = String::new();
    let _ = std::io::stdin().read_line(&mut input)?;
    Ok(input.trim().to_string())
}

fn authenticate(sess: &Session, user_name: Option<&str>) -> SshResult<()> {
    match sess.userauth_none(user_name)? {
        AuthStatus::Success => return Ok(()),
        _ => {}
    }

    loop {
        let auth_methods = sess.userauth_list(user_name)?;

        if auth_methods.contains(AuthMethods::PUBLIC_KEY) {
            match sess.userauth_public_key_auto(None, None)? {
                AuthStatus::Success => return Ok(()),
                _ => {}
            }
        }

        if auth_methods.contains(AuthMethods::INTERACTIVE) {
            loop {
                match sess.userauth_keyboard_interactive(None, None)? {
                    AuthStatus::Success => return Ok(()),
                    AuthStatus::Info => {
                        let info = sess.userauth_keyboard_interactive_info()?;
                        if !info.instruction.is_empty() {
                            eprintln!("{}", info.instruction);
                        }
                        let mut answers = vec![];
                        for p in &info.prompts {
                            answers.push(prompt(&p.prompt, p.echo)?);
                        }
                        sess.userauth_keyboard_interactive_set_answers(&answers)?;

                        continue;
                    }
                    AuthStatus::Denied => {
                        break;
                    }
                    status => {
                        return Err(Error::Fatal(format!(
                            "interactive auth status: {:?}",
                            status
                        )))
                    }
                }
            }
        }

        if auth_methods.contains(AuthMethods::PASSWORD) {
            let pw = prompt("Password: ", false)?;

            match sess.userauth_password(user_name, Some(&pw))? {
                AuthStatus::Success => return Ok(()),
                status => return Err(Error::Fatal(format!("password auth status: {:?}", status))),
            }
        }

        return Err(Error::Fatal("unhandled auth case".to_string()));
    }
}

fn main() -> SshResult<()> {
    let sess = Session::new()?;
    sess.set_auth_callback(|prompt, echo, verify, identity| {
        let prompt = match identity {
            Some(ident) => format!("{} ({}): ", prompt, ident),
            None => prompt.to_string(),
        };
        get_input(&prompt, None, echo, verify)
            .ok_or_else(|| Error::Fatal("reading password".to_string()))
    });

    sess.set_option(SshOption::Hostname("localhost".to_string()))?;
    // sess.set_option(SshOption::LogLevel(LogLevel::Packet))?;
    sess.options_parse_config(None)?;
    sess.connect()?;
    eprintln!(
        "using {} as user name for authentication",
        sess.get_user_name()?
    );
    verify_known_hosts(&sess)?;

    authenticate(&sess, None)?;

    let channel = sess.new_channel()?;
    channel.open_session()?;
    channel.request_exec("whoami")?;
    channel.send_eof()?;

    let mut stdout = String::new();
    channel.stdout().read_to_string(&mut stdout)?;

    eprintln!("whoami -> {}", stdout);

    let res = channel.get_exit_status();
    eprintln!("exit status: {:?}", res);

    Ok(())
}