Skip to content

Add support for SSH interactive authentication #869

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
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
3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ bitflags = "1.1.0"
libc = "0.2"
log = "0.4.8"
libgit2-sys = { path = "libgit2-sys", version = "0.14.0" }
libssh2-sys = { version = "0.2.19", optional = true }

[target."cfg(all(unix, not(target_os = \"macos\")))".dependencies]
openssl-sys = { version = "0.9.0", optional = true }
Expand All @@ -35,7 +36,7 @@ paste = "1"
[features]
unstable = []
default = ["ssh", "https", "ssh_key_from_memory"]
ssh = ["libgit2-sys/ssh"]
ssh = ["libgit2-sys/ssh", "libssh2-sys"]
https = ["libgit2-sys/https", "openssl-sys", "openssl-probe"]
vendored-libgit2 = ["libgit2-sys/vendored"]
vendored-openssl = ["openssl-sys/vendored", "libgit2-sys/vendored-openssl"]
Expand Down
104 changes: 79 additions & 25 deletions src/cred.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,23 @@ use std::mem;
use std::path::Path;
use std::process::{Command, Stdio};
use std::ptr;
use std::str;
use url;

use crate::util::Binding;
use crate::{raw, Config, Error, IntoCString};

/// A structure to represent git credentials in libgit2.
pub struct Cred {
raw: *mut raw::git_cred,
pub enum CredInner {
Cred(*mut raw::git_cred),

#[cfg(feature = "ssh")]
Interactive {
username: String,
},
}

/// A structure to represent git credentials in libgit2.
pub struct Cred(pub(crate) CredInner);

/// Management of the gitcredentials(7) interface.
pub struct CredentialHelper {
/// A public field representing the currently discovered username from
Expand All @@ -29,14 +36,18 @@ pub struct CredentialHelper {
}

impl Cred {
pub(crate) unsafe fn from_raw(raw: *mut raw::git_cred) -> Cred {
Cred(CredInner::Cred(raw))
}

/// Create a "default" credential usable for Negotiate mechanisms like NTLM
/// or Kerberos authentication.
pub fn default() -> Result<Cred, Error> {
crate::init();
let mut out = ptr::null_mut();
unsafe {
try_call!(raw::git_cred_default_new(&mut out));
Ok(Binding::from_raw(out))
Ok(Cred::from_raw(out))
}
}

Expand All @@ -49,7 +60,7 @@ impl Cred {
let username = CString::new(username)?;
unsafe {
try_call!(raw::git_cred_ssh_key_from_agent(&mut out, username));
Ok(Binding::from_raw(out))
Ok(Cred::from_raw(out))
}
}

Expand All @@ -70,7 +81,7 @@ impl Cred {
try_call!(raw::git_cred_ssh_key_new(
&mut out, username, publickey, privatekey, passphrase
));
Ok(Binding::from_raw(out))
Ok(Cred::from_raw(out))
}
}

Expand All @@ -91,7 +102,7 @@ impl Cred {
try_call!(raw::git_cred_ssh_key_memory_new(
&mut out, username, publickey, privatekey, passphrase
));
Ok(Binding::from_raw(out))
Ok(Cred::from_raw(out))
}
}

Expand All @@ -105,7 +116,7 @@ impl Cred {
try_call!(raw::git_cred_userpass_plaintext_new(
&mut out, username, password
));
Ok(Binding::from_raw(out))
Ok(Cred::from_raw(out))
}
}

Expand Down Expand Up @@ -147,49 +158,92 @@ impl Cred {
let mut out = ptr::null_mut();
unsafe {
try_call!(raw::git_cred_username_new(&mut out, username));
Ok(Binding::from_raw(out))
Ok(Cred::from_raw(out))
}
}

/// Create a credential to react to interactive prompts.
///
/// The first argument to the callback is the name of the authentication type
/// (eg. "One-time password"); the second argument is the instruction text.
///
/// The callback can be set using [`RemoteCallbacks::ssh_interactive()`](crate::RemoteCallbacks::ssh_interactive())
#[cfg(any(doc, feature = "ssh"))]
pub fn ssh_interactive(username: String) -> Cred {
Cred(CredInner::Interactive { username })
}

/// Check whether a credential object contains username information.
pub fn has_username(&self) -> bool {
unsafe { raw::git_cred_has_username(self.raw) == 1 }
match self.0 {
CredInner::Cred(inner) => unsafe { raw::git_cred_has_username(inner) == 1 },

#[cfg(feature = "ssh")]
CredInner::Interactive { .. } => true,
}
}

/// Return the type of credentials that this object represents.
pub fn credtype(&self) -> raw::git_credtype_t {
unsafe { (*self.raw).credtype }
match self.0 {
CredInner::Cred(inner) => unsafe { (*inner).credtype },

#[cfg(feature = "ssh")]
CredInner::Interactive { .. } => raw::GIT_CREDTYPE_SSH_INTERACTIVE,
}
}

/// Unwrap access to the underlying raw pointer, canceling the destructor
///
/// Panics if this was created using [`Self::ssh_interactive()`]
pub unsafe fn unwrap(mut self) -> *mut raw::git_cred {
mem::replace(&mut self.raw, ptr::null_mut())
match &mut self.0 {
CredInner::Cred(cred) => mem::replace(cred, ptr::null_mut()),

#[cfg(feature = "ssh")]
CredInner::Interactive { .. } => panic!("git2 cred is not a real libgit2 cred"),
}
}
}

impl Binding for Cred {
type Raw = *mut raw::git_cred;
/// Unwrap access to the underlying inner enum, canceling the destructor
pub(crate) unsafe fn unwrap_inner(mut self) -> CredInner {
match &mut self.0 {
CredInner::Cred(cred) => CredInner::Cred(mem::replace(cred, ptr::null_mut())),

unsafe fn from_raw(raw: *mut raw::git_cred) -> Cred {
Cred { raw }
}
fn raw(&self) -> *mut raw::git_cred {
self.raw
#[cfg(feature = "ssh")]
CredInner::Interactive { username } => CredInner::Interactive {
username: mem::replace(username, String::new()),
},
}
}
}

impl Drop for Cred {
fn drop(&mut self) {
if !self.raw.is_null() {
unsafe {
if let Some(f) = (*self.raw).free {
f(self.raw)
#[allow(irrefutable_let_patterns)]
if let CredInner::Cred(raw) = self.0 {
if !raw.is_null() {
unsafe {
if let Some(f) = (*raw).free {
f(raw)
}
}
}
}
}
}

#[cfg(any(doc, feature = "ssh"))]
/// A server-sent prompt for SSH interactive authentication
pub struct SshInteractivePrompt<'a> {
/// The prompt's name or instruction (human-readable)
pub text: std::borrow::Cow<'a, str>,

/// Whether the user's display should be visible or hidden
/// (usually for passwords)
pub echo: bool,
}

impl CredentialHelper {
/// Create a new credential helper object which will be used to probe git's
/// local credential configuration.
Expand Down
3 changes: 3 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,9 @@ pub use crate::util::IntoCString;
pub use crate::version::Version;
pub use crate::worktree::{Worktree, WorktreeAddOptions, WorktreeLockStatus, WorktreePruneOptions};

#[cfg(any(doc, feature = "ssh"))]
pub use crate::cred::SshInteractivePrompt;

// Create a convinience method on bitflag struct which checks the given flag
macro_rules! is_bit_set {
($name:ident, $flag:expr) => {
Expand Down
Loading