summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorToby Vincent <tobyv13@gmail.com>2023-03-19 20:55:12 -0500
committerToby Vincent <tobyv13@gmail.com>2023-03-19 21:22:13 -0500
commit928995171b8f3f19df96cfd5870f7e88f11c3978 (patch)
tree157e81951b38ae7024b702cd3bf911ac1bf3b951
parentb9890b578d8bcdb0d0a9c12ba3901b4e34514286 (diff)
feat: impl ssh KnownHosts file source
-rw-r--r--Cargo.lock120
-rw-r--r--Cargo.toml1
-rw-r--r--src/lib.rs68
-rw-r--r--src/main.rs12
-rw-r--r--src/ssh.rs45
-rw-r--r--src/tmux.rs4
6 files changed, 226 insertions, 24 deletions
diff --git a/Cargo.lock b/Cargo.lock
index c83a4e7..4e4ea07 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -9,6 +9,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "224afbd727c3d6e4b90103ece64b8d1b67fbb1973b1046c2281eed3f3803f800"
[[package]]
+name = "autocfg"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
+
+[[package]]
name = "base64"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -140,6 +146,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f2cb48b81b1dc9f39676bf99f5499babfec7cd8fe14307f7b3d747208fb5690"
[[package]]
+name = "instant"
+version = "0.1.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
name = "io-lifetimes"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -175,12 +190,48 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "99227334921fae1a979cf0bfdfcc6b3e5ce376ef57e16fb6fb3ea2ed6095f80c"
[[package]]
+name = "libssh2-sys"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2dc8a030b787e2119a731f1951d6a773e2280c660f8ec4b0f5e1505a386e71ee"
+dependencies = [
+ "cc",
+ "libc",
+ "libz-sys",
+ "openssl-sys",
+ "pkg-config",
+ "vcpkg",
+]
+
+[[package]]
+name = "libz-sys"
+version = "1.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9702761c3935f8cc2f101793272e202c72b99da8f4224a19ddcf1279a6450bbf"
+dependencies = [
+ "cc",
+ "libc",
+ "pkg-config",
+ "vcpkg",
+]
+
+[[package]]
name = "linux-raw-sys"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4"
[[package]]
+name = "lock_api"
+version = "0.4.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df"
+dependencies = [
+ "autocfg",
+ "scopeguard",
+]
+
+[[package]]
name = "log"
version = "0.4.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -215,6 +266,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3"
[[package]]
+name = "openssl-sys"
+version = "0.9.81"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "176be2629957c157240f68f61f2d0053ad3a4ecfdd9ebf1e6521d18d9635cf67"
+dependencies = [
+ "autocfg",
+ "cc",
+ "libc",
+ "pkg-config",
+ "vcpkg",
+]
+
+[[package]]
name = "os_str_bytes"
version = "6.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -227,12 +291,43 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
[[package]]
+name = "parking_lot"
+version = "0.11.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99"
+dependencies = [
+ "instant",
+ "lock_api",
+ "parking_lot_core",
+]
+
+[[package]]
+name = "parking_lot_core"
+version = "0.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc"
+dependencies = [
+ "cfg-if",
+ "instant",
+ "libc",
+ "redox_syscall",
+ "smallvec",
+ "winapi",
+]
+
+[[package]]
name = "pin-project-lite"
version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116"
[[package]]
+name = "pkg-config"
+version = "0.3.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160"
+
+[[package]]
name = "proc-macro-error"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -344,6 +439,12 @@ dependencies = [
]
[[package]]
+name = "scopeguard"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
+
+[[package]]
name = "serde"
version = "1.0.156"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -379,6 +480,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0"
[[package]]
+name = "ssh2"
+version = "0.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e7fe461910559f6d5604c3731d00d2aafc4a83d1665922e280f42f9a168d5455"
+dependencies = [
+ "bitflags",
+ "libc",
+ "libssh2-sys",
+ "parking_lot",
+]
+
+[[package]]
name = "sshr"
version = "0.1.0"
dependencies = [
@@ -388,6 +501,7 @@ dependencies = [
"indoc",
"ron",
"serde",
+ "ssh2",
"thiserror",
"tracing",
"tracing-subscriber",
@@ -535,6 +649,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
[[package]]
+name = "vcpkg"
+version = "0.2.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
+
+[[package]]
name = "version_check"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/Cargo.toml b/Cargo.toml
index 86528eb..efe4443 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -15,3 +15,4 @@ directories = "4.0.1"
tracing = "0.1.37"
thiserror = "1.0.40"
tracing-subscriber = { version = "0.3.16", features = ["env-filter"] }
+ssh2 = "0.9.4"
diff --git a/src/lib.rs b/src/lib.rs
index 8d33eac..4bb2310 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,6 +1,11 @@
-use std::{collections::HashMap, fmt::Display, iter::IntoIterator};
+use std::{
+ collections::{hash_map::Entry, HashMap},
+ fmt::Display,
+ iter::IntoIterator,
+ time::Duration,
+};
-use serde::Deserialize;
+use serde::{Deserialize, Serialize};
pub use config::Config;
pub use history::History;
@@ -8,10 +13,9 @@ pub use tmux::Tmux;
mod config;
mod history;
+mod ssh;
mod tmux;
-pub type Timestamp = Option<u64>;
-
pub trait SessionSource {
type Error: std::error::Error;
@@ -21,28 +25,60 @@ pub trait SessionSource {
fn update(
&self,
- mut hash_map: HashMap<String, Timestamp>,
- ) -> Result<HashMap<String, Timestamp>, Self::Error> {
+ mut hash_map: HashMap<String, Session>,
+ ) -> Result<HashMap<String, Session>, Self::Error> {
for session in self.sessions()? {
- hash_map
- .entry(session.name)
- .and_modify(|o| {
- if let Some(timestamp) = session.timestamp {
- *o = o.map(|t| timestamp.max(t));
- }
- })
- .or_insert(session.timestamp);
+ match hash_map.entry(session.name.to_owned()) {
+ Entry::Occupied(mut o) if &session > o.get() => {
+ o.insert(session);
+ }
+ Entry::Vacant(v) => {
+ v.insert(session);
+ }
+ _ => {}
+ }
}
Ok(hash_map)
}
}
-#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Deserialize)]
+#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
+pub enum State {
+ Discovered,
+ Connected,
+ Updated,
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct Session {
- pub timestamp: Timestamp,
+ pub state: State,
+
+ #[serde(with = "epoch_timestamp")]
+ pub timestamp: Duration,
+
pub name: String,
}
+mod epoch_timestamp {
+ use std::time::Duration;
+
+ use serde::{self, Deserialize, Deserializer, Serializer};
+
+ pub fn serialize<S>(duration: &Duration, serializer: S) -> Result<S::Ok, S::Error>
+ where
+ S: Serializer,
+ {
+ serializer.serialize_u64(duration.as_secs())
+ }
+
+ pub fn deserialize<'de, D>(d: D) -> Result<Duration, D::Error>
+ where
+ D: Deserializer<'de>,
+ {
+ u64::deserialize(d).map(Duration::from_secs)
+ }
+}
+
impl Display for Session {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.name)
diff --git a/src/main.rs b/src/main.rs
index 7534bb1..2357ab2 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -2,7 +2,7 @@ use std::collections::HashMap;
use clap::Parser;
-use sshr::{Config, History, Session, SessionSource, Timestamp, Tmux};
+use sshr::{Config, History, Session, SessionSource, Tmux};
fn main() -> anyhow::Result<()> {
let config = Config::parse();
@@ -20,7 +20,10 @@ fn main() -> anyhow::Result<()> {
}
fn list_sessions(config: Config) -> anyhow::Result<()> {
- let mut sessions: HashMap<String, Timestamp> = HashMap::new();
+ let mut sessions = HashMap::new();
+
+ let ssh = ssh2::Session::new()?;
+ sessions = ssh.update(sessions)?;
let tmux = Tmux::new(config.socket_name);
sessions = tmux.update(sessions)?;
@@ -31,10 +34,7 @@ fn list_sessions(config: Config) -> anyhow::Result<()> {
}
}
- let mut sessions: Vec<Session> = sessions
- .into_iter()
- .map(|(name, timestamp)| Session { name, timestamp })
- .collect();
+ let mut sessions: Vec<Session> = sessions.into_values().collect();
sessions.sort();
diff --git a/src/ssh.rs b/src/ssh.rs
new file mode 100644
index 0000000..29c6de3
--- /dev/null
+++ b/src/ssh.rs
@@ -0,0 +1,45 @@
+use std::time::{SystemTime, UNIX_EPOCH};
+
+use directories::UserDirs;
+use ssh2::KnownHostFileKind;
+
+use crate::{Session, SessionSource, State};
+
+impl SessionSource for ssh2::Session {
+ type Error = ssh2::Error;
+
+ type Iter = Vec<Session>;
+
+ fn sessions(&self) -> Result<Self::Iter, Self::Error> {
+ let timestamp = SystemTime::now()
+ .duration_since(UNIX_EPOCH)
+ .expect("Current time is pre-epoch. (Time traveler?)");
+
+ let mut known_hosts = self.known_hosts()?;
+
+ let file = UserDirs::new()
+ .ok_or_else(ssh2::Error::unknown)?
+ .home_dir()
+ .join(".ssh/known_hosts");
+
+ known_hosts.read_file(&file, KnownHostFileKind::OpenSSH)?;
+
+ let sessions = known_hosts
+ .hosts()?
+ .into_iter()
+ .filter_map(|h| match h.name() {
+ Some(name) => Some(Session {
+ state: State::Discovered,
+ timestamp,
+ name: name.to_owned(),
+ }),
+ None => {
+ tracing::warn!("Invalid host: No plain text host name exists");
+ None
+ }
+ })
+ .collect();
+
+ Ok(sessions)
+ }
+}
diff --git a/src/tmux.rs b/src/tmux.rs
index c6e83b1..6c53831 100644
--- a/src/tmux.rs
+++ b/src/tmux.rs
@@ -12,7 +12,7 @@ pub struct Tmux {
}
impl Tmux {
- const SESSION_FORMAT: &str = r##"Session( name: "#S", timestamp: Some(#{?session_last_attached,#{session_last_attached},#{session_created}}))"##;
+ const SESSION_FORMAT: &str = r##"Session(name: "#S", #{?session_last_attached,timestamp: #{session_last_attached}#, state: Updated,timestamp: #{session_created}#, state: Connected})"##;
pub fn new(socket_name: String) -> Self {
Self { socket_name }
@@ -39,7 +39,7 @@ impl SessionSource for Tmux {
.filter_map(|s| match ron::from_str(s) {
Ok(session) => Some(session),
Err(err) => {
- tracing::warn!(?err, "Invalid session format");
+ tracing::warn!(%err, "Invalid session format");
None
}
})