Skip to content

Commit

Permalink
Preparing Basic SASL Supports for LDAP
Browse files Browse the repository at this point in the history
  • Loading branch information
RockfordWei committed Jan 25, 2017
1 parent 709be0b commit 0e57430
Show file tree
Hide file tree
Showing 3 changed files with 143 additions and 26 deletions.
113 changes: 94 additions & 19 deletions Sources/PerfectLDAP.swift
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ public class LDAP {

/// LDAP handler pointer
internal var ldap: OpaquePointer? = nil

/// codepage convertor
internal var iconv: Iconv? = nil

Expand Down Expand Up @@ -137,21 +137,62 @@ public class LDAP {
}//end str
}//end string

public var _supportedControls = [String]()
private var _supportedControl = [String]()
private var _supportedExtension = [String]()
private var _supportedSASLMechanisms = [String]()
private var _saslMech: [AuthType:String] = [:]

public var supportedControl: [String] { get { return _supportedControl } }
public var supportedExtension: [String] { get { return _supportedExtension } }
public var supportedSASLMechanisms: [String] { get { return _supportedSASLMechanisms } }
public var supportedSASL: [AuthType:String] { get { return _saslMech } }

public func withUnsafeSASLDefaultsPointer<R>(mech: String = "", realm: String = "", authcid: String = "", passwd: String = "", authzid: String = "",_ body: (UnsafeMutableRawPointer?) throws -> R) rethrows -> R {
var def = lutilSASLdefaults(mech: nil, realm: nil, authcid: nil, passwd: nil, authzid: nil, resps: nil, nresps: 0)
if mech.isEmpty {
let _ = ldap_get_option(self.ldap, LDAP_OPT_X_SASL_MECH, &(def.mech))
} else {
def.mech = ber_strdup(mech)
}//end if
if realm.isEmpty {
let _ = ldap_get_option(self.ldap, LDAP_OPT_X_SASL_REALM, &(def.realm))
} else {
def.realm = ber_strdup(realm)
}//end if
if authcid.isEmpty {
let _ = ldap_get_option(self.ldap, LDAP_OPT_X_SASL_AUTHCID, &(def.authcid))
} else {
def.authcid = ber_strdup(authcid)
}//end if
if authzid.isEmpty {
let _ = ldap_get_option(self.ldap, LDAP_OPT_X_SASL_AUTHZID, &(def.authzid))
} else {
def.authzid = ber_strdup(authzid)
}//end if
if !passwd.isEmpty {
def.passwd = ber_strdup(passwd)
}//end if

/// load session control OIDs from server
/// - throws:
/// Exception with messages.
public func checkServerSideControls() throws {
guard let r = try self.search() else {
throw Exception.message("load controls failed")
}//end guard
guard let root = r.dictionary[""] else {
throw Exception.message("control has no expected key")
}//end guard
_supportedControls = root["supportedControl"] as? [String] ?? []

}//end func
let r = try body(UnsafeMutablePointer(mutating: &def))

if def.mech != nil {
ber_memfree(def.mech)
}//end if
if def.realm != nil {
ber_memfree(def.realm)
}//end if
if def.authcid != nil {
ber_memfree(def.authcid)
}//end if
if def.authzid != nil {
ber_memfree(def.authzid)
}//end if
if def.passwd != nil {
ber_memfree(def.passwd)
}//end if

return r
}

/// constructor of LDAP. could be a simple LDAP() to local server or LDAP(url) with / without logon options.
/// if login parameters were input, the process would block until finished.
Expand All @@ -178,6 +219,26 @@ public class LDAP {
throw Exception.message(LDAP.error(r))
}//end guard

guard let dse = try search() else {
throw Exception.message("ROOT DSE FAULT")
}//end dse

guard let root = dse.dictionary[""] else {
throw Exception.message("ROOT DSE HAS NO EXPECTED KEY")
}//end root

_supportedControl = root["supportedControl"] as? [String] ?? []
_supportedExtension = root["supportedExtension"] as? [String] ?? []
_supportedSASLMechanisms = root["supportedSASLMechanisms"] as? [String] ?? []

_supportedSASLMechanisms.forEach { mech in
if strstr(mech, "GSSAPI") != nil {
_saslMech[AuthType.GSSAPI] = mech
}else if strstr(mech, "GSS-SPNEGO") != nil {
_saslMech[AuthType.SPNEGO] = mech
}//end if
}//next

// if no login required, skip.
if username == nil || password == nil {
return
Expand All @@ -198,10 +259,24 @@ public class LDAP {
/// true for a successful login.
@discardableResult
public func login(username: String, password: String, auth: AuthType = .SIMPLE) -> Bool {
var cred = berval(bv_len: ber_len_t(password.utf8.count), bv_val: ber_strdup(password))
let r = ldap_sasl_bind_s(self.ldap, username, nil, &cred, nil, nil, nil)
ber_memfree(cred.bv_val)
return r == 0
var r = Int32(0)
switch auth {
case .SIMPLE:
var cred = berval(bv_len: ber_len_t(password.utf8.count), bv_val: ber_strdup(password))
r = ldap_sasl_bind_s(self.ldap, username, nil, &cred, nil, nil, nil)
ber_memfree(cred.bv_val)
return r == 0
case .GSSAPI:
/*
r = ldap_sasl_interactive_bind_s(ldap, username, _saslMech[.GSSAPI], nil, nil, LDAP_SASL_AUTOMATIC, { opaque, uint32, raw1, raw2 in return Int32(0)}, <#T##defaults: UnsafeMutableRawPointer!##UnsafeMutableRawPointer!#>)
**/
return true
case .SPNEGO:
return true
default:
// not support - always failed
return false
}
}//end login

/// Login in asynchronized mode. Once completed, it would invoke the callback handler
Expand Down
9 changes: 5 additions & 4 deletions Sources/Utilities.swift
Original file line number Diff line number Diff line change
Expand Up @@ -53,15 +53,15 @@ extension Array {

let pointers = UnsafeMutablePointer<UnsafeMutablePointer<Element>?>.allocate(capacity: self.count + 1)
pointers.initialize(from: pArray)
pointers.advanced(by: self.count).pointee = UnsafeMutablePointer<Element>(bitPattern: 0)
pointers.advanced(by: self.count).pointee = nil
return pointers
}//func
}//end array

public func withCArrayOfString<R>(array: [String] = [], _ body: (UnsafeMutablePointer<UnsafeMutablePointer<Int8>?>?) -> R) -> R {
public func withCArrayOfString<R>(array: [String] = [], _ body: (UnsafeMutablePointer<UnsafeMutablePointer<Int8>?>?) throws -> R) rethrows -> R {

if array.isEmpty {
return body(nil)
return try body(nil)
}//end if

// duplicate the array and append a null string
Expand All @@ -72,10 +72,11 @@ public func withCArrayOfString<R>(array: [String] = [], _ body: (UnsafeMutablePo
var parr = attr.map { $0 == nil ? nil : strdup($0!) }

// perform the operation
let r = parr.withUnsafeMutableBufferPointer { body ($0.baseAddress) }
let r = try parr.withUnsafeMutableBufferPointer { try body ($0.baseAddress) }

// release allocated string pointers.
for p in parr { free(UnsafeMutablePointer(mutating: p)) }

return r
}//end withCArrayOfString

47 changes: 44 additions & 3 deletions Tests/PerfectLDAPTests/PerfectLDAPTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import XCTest
@testable import PerfectLDAP
import Foundation
import PerfectICONV
import OpenLDAP

class PerfectLDAPTests: XCTestCase {
let testURL = "ldap://192.168.56.13"
Expand Down Expand Up @@ -44,11 +45,15 @@ class PerfectLDAPTests: XCTestCase {
let fail = ldap.login(username: "abc", password: "123")
XCTAssertFalse(fail)

print(" -- -- --- ---")
print(ldap.supportedControl)
print(ldap.supportedExtension)
print(ldap.supportedSASLMechanisms)

print(" -- -- --- ---")
let succ = ldap.login(username: testUSR, password: testPWD)
XCTAssertTrue(succ)

try ldap.checkServerSideControls()
print(ldap._supportedControls)
}catch(let err) {
XCTFail("testLoginSync error: \(err)")
}
Expand Down Expand Up @@ -139,14 +144,50 @@ class PerfectLDAPTests: XCTestCase {

}

func testSASLDefaults () {
do {
let ldap = try LDAP(url: "ldap://192.168.56.13")

let sasl = ldap.supportedSASL
guard let gssapi = sasl[LDAP.AuthType.GSSAPI] else {
XCTFail("GSSAPI FAULT")
return
}
print(gssapi)

guard let spnego = sasl[LDAP.AuthType.SPNEGO] else {
XCTFail("SPNEGO FAULT")
return
}
print(spnego)

let r = ldap.withUnsafeSASLDefaultsPointer(mech: "GSSAPI", realm: "PERFECT") { ptr -> Int in
guard let p = ptr else {
return 0
}//end if
let pdef = unsafeBitCast(p, to: UnsafeMutablePointer<lutilSASLdefaults>.self)
let def = pdef.pointee
let mech = String(cString: def.mech)
let realm = String(cString: def.realm)
XCTAssertEqual(mech, "GSSAPI")
XCTAssertEqual(realm, "PERFECT")
return 100
}//en r
XCTAssertEqual(r, 100)
}catch(let err) {
XCTFail("error: \(err)")
}
}

static var allTests : [(String, (PerfectLDAPTests) -> () throws -> Void)] {
return [
("testLogin", testLogin),
("testLoginSync", testLoginSync),
("testSearch", testSearch),
("testSearchSync", testSearchSync),
("testAttributeMod", testAttributeMod),
("testServerSort", testServerSort)
("testServerSort", testServerSort),
("testSASLDefaults", testSASLDefaults)
]
}
}

0 comments on commit 0e57430

Please sign in to comment.