yodle/src/main.rs

163 lines
4.6 KiB
Rust

// Copyright (c) 2019 Reyk Floeter <contact@reykfloeter.com>
//
// Permission to use, copy, modify, and distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
#[macro_use]
extern crate log;
mod cert;
mod client;
mod server;
use bubblebabble::stablebabble;
use cert::KeyPair;
use getopts::Options;
use log::LevelFilter;
use std::{
env,
io::{Error, ErrorKind, Result},
net::SocketAddr,
path::{Path, PathBuf},
process,
time::Duration,
};
use tokio_libtls::prelude::*;
#[derive(Clone, Debug, Default)]
pub(crate) struct Config {
keypair: Option<KeyPair>,
ca: Option<PathBuf>,
timeout: Option<Duration>,
servername: Option<String>,
address: Option<SocketAddr>,
size_limit: usize,
}
impl Config {
pub fn new() -> Self {
Self {
address: "[::1]:8023".parse().ok(),
servername: Some(env::var("USER").unwrap_or("localhost".to_string())),
timeout: Some(Duration::from_secs(10)),
size_limit: 1_073_741_824,
..Default::default()
}
}
pub fn load_keys(&self) -> Result<(&Path, &Path, &Path)> {
let keypair = self
.keypair
.as_ref()
.ok_or(Error::new(ErrorKind::Other, "keypair"))?;
let key = keypair
.key
.as_ref()
.ok_or(Error::new(ErrorKind::Other, "key"))?;
let ca = self.ca.as_ref().ok_or(Error::new(ErrorKind::Other, "CA"))?;
Ok((&keypair.cert, key, ca))
}
pub fn load_server_options(&self) -> AsyncTlsOptions {
let mut options = AsyncTlsOptions::new();
if let Some(timeout) = self.timeout {
options.timeout(timeout);
}
if let Some(ref servername) = self.servername {
options.servername(servername);
} else {
options.servername("localhost");
}
options
}
pub fn load_client_options(&self) -> AsyncTlsOptions {
self.load_server_options()
}
}
fn usage(program: &str, opts: Options) -> ! {
let brief = format!("Usage: {} [options]", program);
print!("{}", opts.usage(&brief));
process::exit(1)
}
#[tokio::main]
async fn main() {
let args: Vec<String> = env::args().collect();
let program = args[0].clone();
let mut config = Config::new();
let mut opts = Options::new();
opts.optopt(
"a",
"address",
"client/server address",
&config.address.unwrap().to_string(),
);
opts.optopt(
"n",
"name",
"server name",
&config.servername.as_ref().unwrap(),
);
opts.optopt("f", "file", "send file as client", "filename");
opts.optflag("s", "server", "run server");
opts.optflag("h", "help", "print this help menu");
let matches = match opts.parse(&args[1..]) {
Ok(m) => m,
Err(f) => panic!(f.to_string()),
};
if matches.opt_present("h") || (matches.opt_present("f") && matches.opt_present("s")) {
usage(&program, opts);
}
if let Some(address) = matches.opt_str("a") {
let addr: SocketAddr = address.parse().unwrap();
config.address = Some(addr);
}
let addr = match config.address {
Some(SocketAddr::V6(addr)) => addr.clone(),
_ => panic!("invalid address: {:?}", config.address),
};
env_logger::builder()
.filter_level(LevelFilter::Debug)
.init();
let file = if let Some(file) = matches.opt_str("f") {
let file = PathBuf::from(file);
if !file.exists() {
panic!("invalid file: {}", file.display())
}
Some(file)
} else {
None
};
let keypair = KeyPair::new(&config);
config.ca = Some(keypair.cert.clone());
config.keypair = Some(keypair);
debug!(
"{}:{} started",
stablebabble(&addr.ip().octets()),
addr.port()
);
if let Some(file) = file {
client::run(config, file).await.expect("client");
} else {
server::run(config).await.expect("server");
}
}