Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 100 additions & 4 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
//! use ssh2_config::SSHConfig;
//!
//! // Retrieve config for local SSH server
//! let sess = SSHConfig::for_host("127.0.0.1")
//! .with_config_file("/path/to/ssh/config") // equivalent to OpenSSH's `-F`
//! let sess = SSHConfig::from_file("/path/to/ssh/config") // equivalent to OpenSSH's `-F`
//! .for_host("127.0.0.1")
//! .connect_with_auth();
//!
//! // Make sure we're authenticated
Expand Down Expand Up @@ -217,6 +217,8 @@ impl SSHConfig {
let mut config = vec![];

for path in paths {
// TODO: we need to track the "file" boundary, since every
// file starts off as matching everything
config.extend(readconf_depth(
&path,
ReadMeta {
Expand All @@ -234,8 +236,30 @@ impl SSHConfig {
Ok(Self(config))
}

pub fn for_host(_: &str) -> Self {
unimplemented!()
pub fn for_host(self: &Self, host: &str) -> Self {
fn host_matches(h1: &str, h2: &str) -> bool {
h1 == h2 || h1 == "*" || h2 == "*"
}

let mut last_host = None;
return Self(
self.0
.iter()
.filter(|opt| match opt {
SSHOption::Host(h) => {
last_host = Some(h);
false
}
_ => last_host.map(|h| host_matches(h, host)).unwrap_or(true),
})
.map(|opt| opt.clone())
.collect(),
);

// HashMap<std::mem::Discriminant<SSHOption>, SSHOption>
// 1. Get me all the IdentityFile directives
// 2. insert-or-merge
// 3. Iterate over every option and "apply" it to an ssh session
}

pub fn with_config_file(self: &Self, _: &str) -> Self {
Expand Down Expand Up @@ -456,4 +480,76 @@ mod test {
std::mem::discriminant(&super::Error::MaxDepthExceeded),
)
}

#[test]
fn config_matching() {
use SSHOption::*;
let cfg = SSHConfig(vec![
Host(String::from("example.com")),
User(String::from("example_ssh_user")),
Host(String::from("*")),
User(String::from("ssh_user")),
]);

assert_eq!(
cfg.for_host("127.0.0.1").0,
vec![User(String::from("ssh_user"))]
);
assert_eq!(
cfg.for_host("example.com").0,
vec![User(String::from("example_ssh_user"))]
);
}

#[test]
fn whatsup() {
use std::collections::HashMap;

let input = vec![
SSHOption::User(String::from("seth")),
SSHOption::Port(16),
SSHOption::User(String::from("eric")),
];

let mut mapping: HashMap<std::mem::Discriminant<SSHOption>, SSHOption> = Default::default();

for opt in input {
use option::Merge;
let k = std::mem::discriminant(&opt);

if let Some(last) = mapping.insert(k, opt) {
let opt = mapping.remove(&k).unwrap();
let merged = last.merge(opt);
assert!(mapping.insert(k, merged).is_none());
}
}

// do something
if true {
panic!("{:?}", mapping);
}

// mapping[std::mem::discriminant(&SendEnv(vec![]))]

// mapping[option::Kind::Port]

let output = Vec::<SSHOption>::new();
assert_eq!(
output,
vec![SSHOption::User(String::from("seth")), SSHOption::Port(16),]
);

let mut mapping: HashMap<std::mem::Discriminant<SSHOption>, SSHOption> = Default::default();

assert!(mapping
.insert(
std::mem::discriminant(&SSHOption::User(String::from("this is ignored"))),
SSHOption::User(String::from("seth")),
)
.is_none());

// mapping.insert()

panic!("{:?}", mapping)
}
}
153 changes: 147 additions & 6 deletions src/option.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,25 +12,166 @@ use std::str::MatchIndices;
/// For details on the individual meaning of these options, see [ssh_config(5)][0].
///
/// [0]: https://man.openbsd.org/OpenBSD-current/man5/ssh_config.5
#[derive(Debug, PartialEq, Eq)]
#[derive(Debug, PartialEq, Eq, Clone)]
#[allow(missing_docs)]
#[non_exhaustive]
// TODO: OsString for paths & env vars?
// TODO: OsString for env vars?
pub enum SSHOption {
User(String),
User(User),
Port(u16),
Hostname(String),

Host(String),
SendEnv(Vec<Env>),
SendEnv(SendEnv),
Include(Include),
// IdentityFile(String)
Foo { five: u8 },
}

#[derive(Debug, PartialEq, Eq)]
struct Opt<T>
where
T: Kind + Eq,
{
kind: T,
data: T::Data,
}

trait Kind {
type Data;

fn merge(a: Self::Data, _b: Self::Data) -> Self::Data {
a
}
}

#[derive(Debug, PartialEq, Eq, Hash)]
struct Magic<T: 'static> {
__marker: std::marker::PhantomData<&'static T>,
}

const USER: Magic<User2> = Magic {
__marker: std::marker::PhantomData,
};
const SENDENV: Magic<SendEnv2> = Magic {
__marker: std::marker::PhantomData,
};

#[derive(Debug, PartialEq, Eq, Hash)]
struct User2;

impl Kind for User2 {
type Data = String;
}

#[derive(Debug, PartialEq, Eq, Hash)]
struct SendEnv2;

impl Kind for SendEnv2 {
type Data = Vec<Env>;

fn merge(a: Self::Data, b: Self::Data) -> Self::Data {
let mut both = vec![];
both.extend(a);
both.extend(b);
both
}
}

#[test]
fn foob() {
let foo = Opt::<User2> {
kind: User2,
data: String::from("hi"),
};

let bar = Opt::<SendEnv2> {
kind: SendEnv2,
data: vec![Env::Send(String::from("hi"))],
};

use std::collections::HashMap;
let mut map: HashMap<_, _> = HashMap::new();
assert!(map.insert(USER, foo).is_none());
assert!(map.insert(SENDENV, bar).is_none());

panic!("{:?}", map);
}

// enum Kind {
// User,
// Port,
// Hostname,
// };

type User = String;
type SendEnv = Vec<Env>;

#[allow(missing_docs)]
pub trait Merge
where
Self: std::marker::Sized,
{
fn merge(self: Self, _other: Self) -> Self {
self
}
}

// struct Merged<T> {

// }

impl Merge for SSHOption {
fn merge(self: Self, other: Self) -> Self {
match (self, other) {
(a @ SSHOption::User(_), SSHOption::User(_)) => a,
(SSHOption::User(_), _) => todo!(),

(_, _) => todo!(),
}
}
}

impl Merge for User {}

// impl Merge for SendEnv {
// fn merge(a: SendEnv, b: SendEnv) -> SendEnv {
// // a.into_iter().concat(b.into_iter()).collect()
// let mut both = vec![];
// both.extend(a);
// both.extend(b);
// both
// }
// }

// fn merge_two_variants<T: variant_of_ssh_option>(a: T, b: T) {

// }

#[allow(dead_code)]
mod experiment {
use super::*;
fn merge_two_sendenvs(mut a: SendEnv, b: SendEnv) -> SendEnv {
a.extend(b);
a
}

fn merge_two_users(a: User, _b: User) -> User {
a
}

// TODO: this is a problem, maybe
type TODO = ();
fn merge_two_includes(_a: Include, _b: Include) -> TODO {
todo!()
}
}

/// Env represents an environment variable pattern for the `SendEnv` directive.
///
/// SendEnv may either express a positive or negative pattern to send along or
/// stop the sending of a variable respectively.
#[derive(Debug, PartialEq, Eq)]
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum Env {
/// Send is a positive pattern: "send along matching env vars"
Send(String),
Expand All @@ -41,7 +182,7 @@ pub enum Env {
/// Include represents the nested config structure provided by an include directive.
///
/// We preserve the nested structure in order to handle nested Host/Match states.
#[derive(Debug, PartialEq, Eq)]
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum Include {
/// Paths is a list of file patterns to include.
Paths(Vec<String>),
Expand Down