Skip to content
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

Add support for GSS-SPNEGO bind #33

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions Cargo.toml
Expand Up @@ -32,6 +32,9 @@ tokio-uds-proto = "0.1"
path = "lber"
version = "0.1.6"

[dependencies.sspi]
version = "0.1.1"

[dependencies.tokio-tls]
version = "0.2"
optional = true
Expand Down
26 changes: 26 additions & 0 deletions examples/connect_spnego.rs
@@ -0,0 +1,26 @@
extern crate ldap3;

use std::error::Error;
use std::env;

use ldap3::LdapConn;

fn main() {
let args: Vec<String> = env::args().collect();
let server_url = args.get(1).unwrap();
let username = args.get(2).unwrap();
let password = args.get(3).unwrap();

println!("server_url: {} username: {} password: {}", server_url, username, password);

match do_connection(server_url, username, password) {
Ok(_) => (),
Err(e) => println!("{:?}", e),
}
}

fn do_connection(server_url: &str, username: &str, password: &str) -> Result<(), Box<Error>> {
let ldap = LdapConn::new(server_url)?;
let _response = ldap.sasl_spnego_bind(username, password)?.success()?;
Ok(())
}
80 changes: 80 additions & 0 deletions src/bind_spnego.rs
@@ -0,0 +1,80 @@
use std::io;

use lber::structures::{Tag, Sequence, Integer, OctetString};
use lber::common::TagClass;

use futures::{Future};
use tokio_service::Service;

use ldap::{Ldap, LdapOp, next_req_controls};
use result::LdapResult;

use spnego;

pub const GSS_SPNEGO: &'static str = "GSS-SPNEGO";

fn create_bind_request(token: Vec<u8>) -> Tag {
Tag::Sequence(Sequence {
id: 0,
class: TagClass::Application,
inner: vec![
Tag::Integer(Integer {
inner: 3,
.. Default::default()
}),
Tag::OctetString(OctetString {
inner: Vec::new(),
.. Default::default()
}),
Tag::Sequence(Sequence {
id: 3,
class: TagClass::Context,
inner: vec![
Tag::OctetString(OctetString {
inner: Vec::from(GSS_SPNEGO),
.. Default::default()
}),
Tag::OctetString(OctetString {
inner: token.to_vec(),
.. Default::default()
}),
]
})
],
})
}

impl Ldap {
/// See [`LdapConn::sasl_spnego_bind()`](struct.LdapConn.html#method.sasl_spnego_bind).
pub fn sasl_spnego_bind(&self, username: &str, password: &str) ->
Box<Future<Item=LdapResult, Error=io::Error>> {
let mut spnego_client = spnego::Client::new(username, password);

let input = Vec::new();
let mut output = Vec::new();
let _sspi_status = spnego_client.authenticate(input.as_slice(), &mut output).unwrap();
let req = create_bind_request(output.clone());

let ldap = self.clone();
let fut = self.call(LdapOp::Single(req, next_req_controls(self)))
.and_then(move |response| {

let (mut result, controls) = (LdapResult::from(response.0), response.1);
result.ctrls = controls;

let input = result.get_bind_token().unwrap();
let mut output = Vec::new();
let _sspi_status = spnego_client.authenticate(input.as_slice(), &mut output).unwrap();
let req = create_bind_request(output.clone());

ldap.call(LdapOp::Single(req, next_req_controls(&ldap)))
.and_then(|response| {
let (mut result, controls) = (LdapResult::from(response.0), response.1);
result.ctrls = controls;
Ok(result)
})
});

Box::new(fut)
}
}
5 changes: 5 additions & 0 deletions src/conn.rs
Expand Up @@ -229,6 +229,11 @@ impl LdapConn {
Ok(self.core.borrow_mut().run(self.inner.clone().sasl_external_bind())?)
}

/// Do a SASL GSS-SPNEGO bind (SSPI Negotiate module, NTLM or Kerberos)
pub fn sasl_spnego_bind(&self, username: &str, password: &str) -> io::Result<LdapResult> {
Ok(self.core.borrow_mut().run(self.inner.clone().sasl_spnego_bind(username, password))?)
}

/// Use the provided `SearchOptions` with the next Search operation, which can
/// be invoked directly on the result of this method. If this method is used in
/// combination with a non-Search operation, the provided options will be silently
Expand Down
25 changes: 23 additions & 2 deletions src/controls_impl/mod.rs
Expand Up @@ -9,17 +9,18 @@ pub mod types {
//!
//! Variants are individually reexported from the private submodule
//! to inhibit exhaustive matching.
pub use self::inner::_ControlType::{PagedResults, PostReadResp, PreReadResp};
pub use self::inner::_ControlType::{PagedResults, PostReadResp, PreReadResp, BindResponse};

/// Recognized control types. Variants can't be named in the namespace
/// of this type; they must be used through module-level reexports.
pub type ControlType = self::inner::_ControlType;
mod inner {
#[derive(Clone, Copy, Debug)]
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum _ControlType {
PagedResults,
PostReadResp,
PreReadResp,
BindResponse,
#[doc(hidden)]
_Nonexhaustive,
}
Expand Down Expand Up @@ -205,3 +206,23 @@ pub fn parse_controls(t: StructureTag) -> Vec<Control> {
}
ctrls
}

use lber::common::TagClass;
pub const SPNEGO_MECH_OID: &'static str = "1.3.6.1.5.5.2";

pub fn parse_bind_response(t: StructureTag) -> Vec<Control> {
let mut ctrls = Vec::new();
let tags = t.clone().expect_constructed().expect("result sequence").into_iter();
for tag in tags {
if tag.class == TagClass::Context && tag.id == 7 {
// serverSaslCreds [7] OCTET STRING OPTIONAL
let token = tag.expect_primitive().unwrap().clone();
ctrls.push(Control(Some(types::BindResponse), RawControl {
ctype: SPNEGO_MECH_OID.to_string(),
crit: false,
val: Some(token.clone())
}));
}
}
ctrls
}
4 changes: 4 additions & 0 deletions src/lib.rs
Expand Up @@ -138,9 +138,13 @@ extern crate tokio_uds;
extern crate tokio_uds_proto;
extern crate url;

extern crate sspi;
mod spnego;

mod abandon;
mod add;
mod bind;
mod bind_spnego;
mod compare;
mod conn;
pub mod controls {
Expand Down
7 changes: 6 additions & 1 deletion src/protocol.rs
Expand Up @@ -22,7 +22,7 @@ use lber::universal::Types;
use lber::write;

use controls::Control;
use controls_impl::{parse_controls, build_tag};
use controls_impl::{parse_controls, parse_bind_response, build_tag};
use exop::Exop;
use ldap::LdapOp;
use result::LdapResult;
Expand Down Expand Up @@ -218,6 +218,11 @@ impl Decoder for LdapCodec {
Ok(Some((id, (id_tag, vec![]))))
}
},
1 => {
let controls = parse_bind_response(protoop.clone());
self.bundle.borrow_mut().id_map.remove(&msgid);
Ok(Some((id, (Tag::StructureTag(protoop), controls))))
},
_ => {
self.bundle.borrow_mut().id_map.remove(&msgid);
Ok(Some((id, (Tag::StructureTag(protoop), controls))))
Expand Down
24 changes: 23 additions & 1 deletion src/result.rs
Expand Up @@ -11,7 +11,7 @@ use std::fmt;
use std::io;
use std::result::Result;

use controls::Control;
use controls::{Control,types};
use exop::Exop;
use protocol::LdapResultExt;
use search::ResultEntry;
Expand Down Expand Up @@ -140,6 +140,28 @@ impl LdapResult {
Err(io::Error::new(io::ErrorKind::Other, self))
}
}

/// If the result code is 0 or 14 (saslBindInProgress), return the instance
/// itself wrapped in `Ok()`, otherwise wrap the instance in an
/// `io::Error`.
pub fn bind_ok(self) -> Result<Self, io::Error> {
if self.rc == 0 || self.rc == 14 {
Ok(self)
} else {
Err(io::Error::new(io::ErrorKind::Other, self))
}
}

pub fn get_bind_token(&mut self) -> Option<Vec<u8>> {
for ctrl in &self.ctrls {
if ctrl.0 == Some(types::BindResponse) {
if let Some(val) = ctrl.1.val.clone() {
return Some(val);
}
}
}
None
}
}

/// Wrapper for results of a Search operation which returns all entries at once.
Expand Down
25 changes: 25 additions & 0 deletions src/spnego.rs
@@ -0,0 +1,25 @@
use std::io;

use sspi::ntlm::*;
use sspi::sspi::{Sspi};

pub struct Client {
sspi_module: Ntlm,
}

impl Client {
pub fn new(username: &str, password: &str) -> Self {
let credentials = sspi::Credentials::new(username.to_string(), password.to_string(), None);
let mut sspi_module = sspi::ntlm::Ntlm::new(Some(credentials));
sspi_module.set_confidentiality(false);
sspi_module.set_integrity(false);

Client {
sspi_module: sspi_module,
}
}

pub fn authenticate(&mut self, input: impl io::Read, output: impl io::Write) -> sspi::SspiResult {
self.sspi_module.initialize_security_context(input, output)
}
}