-
Notifications
You must be signed in to change notification settings - Fork 70
/
client.rs
80 lines (65 loc) · 2.27 KB
/
client.rs
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
use std::error::Error as StdError;
use std::io;
use std::net::ToSocketAddrs;
use std::path::PathBuf;
use std::sync::Arc;
use argh::FromArgs;
use rustls::pki_types::pem::PemObject;
use rustls::pki_types::{CertificateDer, ServerName};
use tokio::io::{copy, split, stdin as tokio_stdin, stdout as tokio_stdout, AsyncWriteExt};
use tokio::net::TcpStream;
use tokio_rustls::{rustls, TlsConnector};
/// Tokio Rustls client example
#[derive(FromArgs)]
struct Options {
/// host
#[argh(positional)]
host: String,
/// port
#[argh(option, short = 'p', default = "443")]
port: u16,
/// domain
#[argh(option, short = 'd')]
domain: Option<String>,
/// cafile
#[argh(option, short = 'c')]
cafile: Option<PathBuf>,
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn StdError + Send + Sync + 'static>> {
let options: Options = argh::from_env();
let addr = (options.host.as_str(), options.port)
.to_socket_addrs()?
.next()
.ok_or_else(|| io::Error::from(io::ErrorKind::NotFound))?;
let domain = options.domain.unwrap_or(options.host);
let content = format!("GET / HTTP/1.0\r\nHost: {}\r\n\r\n", domain);
let mut root_cert_store = rustls::RootCertStore::empty();
if let Some(cafile) = &options.cafile {
for cert in CertificateDer::pem_file_iter(cafile)? {
root_cert_store.add(cert?)?;
}
} else {
root_cert_store.extend(webpki_roots::TLS_SERVER_ROOTS.iter().cloned());
}
let config = rustls::ClientConfig::builder()
.with_root_certificates(root_cert_store)
.with_no_client_auth(); // i guess this was previously the default?
let connector = TlsConnector::from(Arc::new(config));
let stream = TcpStream::connect(&addr).await?;
let (mut stdin, mut stdout) = (tokio_stdin(), tokio_stdout());
let domain = ServerName::try_from(domain.as_str())?.to_owned();
let mut stream = connector.connect(domain, stream).await?;
stream.write_all(content.as_bytes()).await?;
let (mut reader, mut writer) = split(stream);
tokio::select! {
ret = copy(&mut reader, &mut stdout) => {
ret?;
},
ret = copy(&mut stdin, &mut writer) => {
ret?;
writer.shutdown().await?
}
}
Ok(())
}