use clap::{SubCommand, Arg, ArgMatches};
use error::{ErrorKind, Error};
use commands::{BasicOptions, StaticSubcommand, remote};
use std::io::{Write,stderr};
use std::process::exit;
use meta;
use meta::KeyType;
use thrussh;
use bincode;
use futures;
use thrussh::{client, key, ChannelId};
use futures::Future;
use std::rc::Rc;
use libpijul;
use regex::Regex;
use thrussh_keys;
use std;
use super::ask;
pub fn invocation() -> StaticSubcommand {
return SubCommand::with_name("keys")
.about("Manage signing and SSH keys")
.arg(Arg::with_name("repository")
.long("repository")
.help("The repository where to record, defaults to the current directory.")
.takes_value(true)
.required(false))
.arg(Arg::with_name("generate-signing-key")
.long("generate-signing")
.help("Generate a signing key.")
.takes_value(false)
.required(false))
.arg(Arg::with_name("port")
.long("port")
.short("p")
.help("Port of the SSH server.")
.takes_value(true)
.required(false))
.arg(Arg::with_name("upload")
.long("upload-to")
.help("Upload your public signing key to a server.")
.takes_value(true)
.required(false))
.arg(Arg::with_name("generate-SSH-key")
.long("generate-ssh")
.help("Generate an SSH key.")
.takes_value(false)
.required(false))
.arg(Arg::with_name("local")
.long("local")
.help("Save keys for this repository only")
.takes_value(false)
.required(false));
}
pub struct Params<'a> {
pub generate_signing_key: bool,
pub generate_ssh_key: bool,
pub server: Option<&'a str>,
pub local: bool,
pub port: u16,
}
pub fn parse_args<'a>(args: &'a ArgMatches) -> Params<'a> {
Params {
generate_signing_key: args.is_present("generate-signing-key"),
generate_ssh_key: args.is_present("generate-SSH-key"),
server: args.value_of("upload"),
local: args.is_present("local"),
port: args.value_of("port").and_then(|x| x.parse().ok()).unwrap_or(22),
}
}
pub fn run(arg_matches: &ArgMatches) -> Result<(), Error> {
let args = parse_args(arg_matches);
let dot_pijul = if args.local {
let opts = BasicOptions::from_args(arg_matches)?;
Some(opts.repo_dir())
} else {
None
};
if args.generate_signing_key {
if let Some(ref dot_pijul) = dot_pijul {
meta::generate_key(&dot_pijul, KeyType::Signing)?
} else {
meta::generate_global_key(KeyType::Signing)?
}
}
if let Some(ref user_host) = args.server {
match meta::load_global_or_local_key(dot_pijul.as_ref(), KeyType::Signing) {
Ok(key) => {
let config = Rc::new(thrussh::client::Config::default());
let ssh_user_host = Regex::new(r"^([^@]*)@(.*)$").unwrap();
if !ssh_user_host.is_match(user_host) {
return Err(ErrorKind::CannotParseRemote.into())
}
let cap = ssh_user_host.captures(user_host).unwrap();
let user = &cap[1];
let server = &cap[2];
let client = SshClient {
exit_status: None,
key_pair: key,
host: server.to_string(),
port: args.port
};
let (path_sec, path_pub) = remote::guess_secret_key_path(None);
let key = thrussh_keys::load_secret_key(
path_sec.to_str().unwrap(),
None,
path_pub.as_ref().map(|x| x.as_path())
)?;
thrussh::client::connect(
(server, args.port), config, None, client,
|connection| {
connection.authenticate_key(user, key)
.and_then(|session| {
session.channel_open_session().and_then(|(mut session, channelid)| {
session.exec(channelid, false, &format!("pijul challenge"));
session.flush().unwrap();
session.wait(move |session| {
session.handler().exit_status.is_some()
})
})
})
}
).unwrap();
}
Err(e) => {
panic!("Could not find key: {:?}", e)
}
}
}
if args.generate_ssh_key {
if let Some(ref dot_pijul) = dot_pijul {
meta::generate_key(&dot_pijul, KeyType::SSH)?
} else {
meta::generate_global_key(KeyType::SSH)?
}
}
Ok(())
}
pub fn explain(r: Result<(), Error>) {
if let Err(Error(kind, _)) = r {
if let ErrorKind::InARepository(p) = kind {
writeln!(stderr(), "Repository {} already exists", p.display()).unwrap();
} else {
writeln!(stderr(), "error: {}", kind).unwrap();
}
exit(1)
}
}
struct SshClient {
exit_status: Option<u32>,
key_pair: libpijul::signature::KeyPair,
host: String,
port: u16,
}
impl client::Handler for SshClient {
type Error = Error;
type FutureBool = futures::Finished<(Self, bool), Self::Error>;
type FutureUnit = futures::Finished<Self, Self::Error>;
type SessionUnit = futures::Finished<(Self, client::Session), Self::Error>;
fn check_server_key(self, server_public_key: &key::PublicKey) -> Self::FutureBool {
let path = std::env::home_dir().unwrap().join(".ssh").join("known_hosts");
match thrussh_keys::check_known_hosts_path(&self.host, self.port, &server_public_key, &path) {
Ok(true) => futures::done(Ok((self, true))),
Ok(false) => {
if let Ok(false) = ask::ask_learn_ssh(&self.host,
self.port, "") {
futures::done(Ok((self, false)))
} else {
thrussh_keys::learn_known_hosts_path(&self.host,
self.port,
&server_public_key,
&path)
.unwrap();
futures::done(Ok((self, true)))
}
}
Err(e) => if let thrussh_keys::ErrorKind::KeyChanged(line) = *e.kind() {
println!("Host key changed! Someone might be eavesdropping this communication, \
refusing to continue. Previous key found line {}",
line);
futures::done(Ok((self, false)))
} else {
futures::done(Err(From::from(e)))
}
}
}
fn data(self, channel: ChannelId, _: Option<u32>, data: &[u8], mut session: client::Session)
-> Self::SessionUnit {
let signature = self.key_pair.sign(data);
session.data(channel, None, &bincode::serialize(&signature, bincode::Infinite).unwrap()).unwrap();
futures::finished((self, session))
}
fn exit_status(mut self,
channel: thrussh::ChannelId,
exit_status: u32,
session: thrussh::client::Session)
-> Self::SessionUnit {
debug!("exit_status received on channel {:?}: {:?}:", channel, exit_status);
self.exit_status = Some(exit_status);
debug!("self.exit_status = {:?}", self.exit_status);
futures::finished((self, session))
}
}