diff --git a/Package.swift b/Package.swift index 07f042d..ae68760 100644 --- a/Package.swift +++ b/Package.swift @@ -4,7 +4,9 @@ let package = Package( name: "PerfectLDAP", dependencies:[ .Package(url: "https://github.com/PerfectSideRepos/Perfect-ICONV.git", majorVersion: 1), + .Package(url: "https://github.com/PerfectSideRepos/Perfect-CArrayHelper.git", majorVersion: 1), .Package(url:"https://github.com/PerfectlySoft/Perfect-Thread.git", majorVersion: 2), + .Package(url:"https://github.com/PerfectlySoft/Perfect-libSASL.git", majorVersion: 1), .Package(url:"https://github.com/PerfectlySoft/Perfect-OpenLDAP.git", majorVersion: 1) ] ) diff --git a/Sources/PerfectLDAP.swift b/Sources/PerfectLDAP.swift index 29c52ac..7f87fa6 100644 --- a/Sources/PerfectLDAP.swift +++ b/Sources/PerfectLDAP.swift @@ -17,6 +17,9 @@ //===----------------------------------------------------------------------===// // +/// C library of SASL +import SASL + /// C library of OpenLDAP import OpenLDAP @@ -26,6 +29,9 @@ import PerfectThread /// Iconv import PerfectICONV +/// CArray Helper +import PerfectCArray + /// Perfect LDAP Module public class LDAP { @@ -205,7 +211,7 @@ public class LDAP { /// - codePage: object server coding page, e.g., GB2312, BIG5 or JS /// - throws: /// possible exceptions of initial failed or access denied - public init(url:String = "ldap://localhost", username: String? = nil, password: String? = nil, auth: AuthType = .SIMPLE, codePage: Iconv.CodePage = .UTF8) throws { + public init(url:String = "ldap://localhost", username: String? = nil, password: String? = nil, realm: String? = nil, auth: AuthType = .SIMPLE, codePage: Iconv.CodePage = .UTF8) throws { if codePage != .UTF8 { // we need a pair of code pages to transit in both directions. @@ -245,11 +251,11 @@ public class LDAP { }//end if // call login internally - guard login(username: username ?? "", password: password ?? "", auth: auth) else { - throw Exception.message("Access Denied") - }//end _login + try login(username: username ?? "", password: password ?? "", realm: realm ?? "", auth: auth) }//end init + + /// login in synchronized mode, will block the calling thread /// - parameters: /// - username: String, user name to login, optional. @@ -258,25 +264,65 @@ public class LDAP { /// - returns: /// true for a successful login. @discardableResult - public func login(username: String, password: String, auth: AuthType = .SIMPLE) -> Bool { + public func login(username: String, password: String, realm: String = "", auth: AuthType = .SIMPLE) throws { var r = Int32(0) + let ex = Exception.message("UNSUPPORTED SECURITY MECH") 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 + case .GSSAPI, .SPNEGO: + guard let mech = _saslMech[auth] else { + throw ex + }//end + r = self.withUnsafeSASLDefaultsPointer(mech: mech, realm: realm, authcid: username, passwd: password) { ldap_sasl_interactive_bind_s(ldap, username, _saslMech[.GSSAPI], nil, nil, LDAP_SASL_AUTOMATIC, { ldapHandle, flags, defaults, input in + + guard flags != LDAP_SASL_QUIET else { + return LDAP_OTHER + }//END GUARD + + let defaultsPointer = unsafeBitCast(defaults, to: UnsafeMutablePointer.self) + let def = defaultsPointer.pointee + + guard let pInput = input else { + return -1 + }//end guard + + var cursor: UnsafeMutablePointer = unsafeBitCast(pInput, to: UnsafeMutablePointer.self) + + var interact = cursor.pointee + + while(Int32(interact.id) != SASL_CB_LIST_END) { + + switch(Int32(interact.id)) { + case SASL_CB_GETREALM: + SASLReply(pInteract: cursor, pDefaults: defaultsPointer, pMsg: def.realm) + case SASL_CB_AUTHNAME: + SASLReply(pInteract: cursor, pDefaults: defaultsPointer, pMsg: def.authcid) + case SASL_CB_PASS: + SASLReply(pInteract: cursor, pDefaults: defaultsPointer, pMsg: def.passwd) + case SASL_CB_USER: + SASLReply(pInteract: cursor, pDefaults: defaultsPointer, pMsg: def.authzid) + case SASL_CB_NOECHOPROMPT, SASL_CB_ECHOPROMPT: // skipped + () + default: //unknown + return -1 + }//end case + cursor.pointee = interact + cursor = cursor.successor() + interact = cursor.pointee + }//end while + + return Int32(0)}, $0) } default: - // not support - always failed - return false + throw ex } + if r == 0 { + return + }else { + throw Exception.message(LDAP.error(r)) + }//end }//end login /// Login in asynchronized mode. Once completed, it would invoke the callback handler @@ -285,10 +331,14 @@ public class LDAP { /// - password: String, password for login, optional. /// - auth: AuthType, such as SIMPLE, GSSAPI, SPNEGO or DIGEST MD5 /// - completion: callback handler with a boolean parameter indicating whether login succeeded or not. - public func login(username: String, password: String, auth: AuthType = .SIMPLE, completion: @escaping (Bool)->Void) { + public func login(username: String, password: String, realm: String = "", auth: AuthType = .SIMPLE, completion: @escaping (String?)->Void) { Threading.dispatch { - let r = self.login(username: username, password: password, auth: auth) - completion(r) + do { + try self.login(username: username, password: password, realm: realm, auth: auth) + completion(nil) + }catch(let err) { + completion("LOGIN FAILED: \(err)") + } }//end thread }//end login diff --git a/Sources/Utilities.swift b/Sources/Utilities.swift index 5f43854..bd8305f 100644 --- a/Sources/Utilities.swift +++ b/Sources/Utilities.swift @@ -19,6 +19,9 @@ import PerfectICONV import OpenLDAP +import PerfectCArray +/// C library of SASL +import SASL extension Iconv { @@ -80,3 +83,31 @@ public func withCArrayOfString(array: [String] = [], _ body: (UnsafeMutablePo return r }//end withCArrayOfString +public var CSTRHelper = CArray>() + +public func SASLReply(pInteract: UnsafeMutablePointer, pDefaults: UnsafeMutablePointer, pMsg: UnsafeMutablePointer) { + + var defaults = pDefaults.pointee + var interact = pInteract.pointee + + interact.len = UInt32(strlen(pMsg)) + if interact.len < 1 { + interact.result = unsafeBitCast(pMsg, to: UnsafeRawPointer.self) + return + }//end if + + var resps = defaults.resps + let _ = CSTRHelper.append(pArray: &resps, element: pMsg) + defaults.resps = resps + interact.result = CSTRHelper.withArray(of: defaults.resps) { array -> UnsafeRawPointer! in + return unsafeBitCast(array[array.count - 1]!, to: UnsafeRawPointer.self) + }//end result +}//end reply + + + + + + + + diff --git a/Tests/PerfectLDAPTests/PerfectLDAPTests.swift b/Tests/PerfectLDAPTests/PerfectLDAPTests.swift index 2c9c2f3..3b94043 100644 --- a/Tests/PerfectLDAPTests/PerfectLDAPTests.swift +++ b/Tests/PerfectLDAPTests/PerfectLDAPTests.swift @@ -8,13 +8,14 @@ class PerfectLDAPTests: XCTestCase { let testURL = "ldap://192.168.56.13" let testUSR = "rocky@p.com" let testPWD = "rockford" + let testRLM = "P" func testLogin() { do { let logfail = expectation(description: "logfail") let ldap = try LDAP(url: testURL, codePage: .GB2312) - ldap.login(username: "abc", password: "123") { result in - XCTAssertFalse(result) + ldap.login(username: "abc", password: "123") { err in + XCTAssertNotNil(err) logfail.fulfill() print("log failed passed") }//end log @@ -24,8 +25,8 @@ class PerfectLDAPTests: XCTestCase { }//end wait let logsuc = expectation(description: "logsuc") - ldap.login(username: testUSR, password: testPWD) { result in - XCTAssertTrue(result) + ldap.login(username: testUSR, password: testPWD) { err in + XCTAssertNil(err) logsuc.fulfill() print("log real passed") }//end log @@ -42,18 +43,20 @@ class PerfectLDAPTests: XCTestCase { func testLoginSync() { do { let ldap = try LDAP(url: testURL, codePage: .GB2312) - let fail = ldap.login(username: "abc", password: "123") - XCTAssertFalse(fail) + try ldap.login(username: "abc", password: "123") print(" -- -- --- ---") print(ldap.supportedControl) print(ldap.supportedExtension) print(ldap.supportedSASLMechanisms) - - print(" -- -- --- ---") - let succ = ldap.login(username: testUSR, password: testPWD) - XCTAssertTrue(succ) + }catch { + // bad password is supposed to fail. + } + do { + let ldap = try LDAP(url: testURL, codePage: .GB2312) + print(" -- -- --- ---") + try ldap.login(username: testUSR, password: testPWD) }catch(let err) { XCTFail("testLoginSync error: \(err)") } @@ -62,7 +65,7 @@ class PerfectLDAPTests: XCTestCase { func testSearch () { do { - let ldap = try LDAP(url: "ldap://192.168.56.13", username: testUSR, password: testPWD, codePage: .GB2312) + let ldap = try LDAP(url: testURL, username: testUSR, password: testPWD, codePage: .GB2312) let ser = expectation(description: "search") ldap.search(base:"cn=users,dc=p,dc=com", scope:.SUBTREE) { res in @@ -83,7 +86,7 @@ class PerfectLDAPTests: XCTestCase { } func testSearchSync () { do { - let ldap = try LDAP(url: "ldap://192.168.56.13", username: testUSR, password: testPWD, codePage: .GB2312) + let ldap = try LDAP(url: testURL, username: testUSR, password: testPWD, codePage: .GB2312) guard let rs = try ldap.search(base:"cn=users,dc=p,dc=com",filter: "(initials=RW)", scope:.SUBTREE, attributes: ["cn", "company", "displayName", "initials"]) else { XCTFail("search failed") return @@ -98,7 +101,7 @@ class PerfectLDAPTests: XCTestCase { func testServerSort () { do { - let ldap = try LDAP(url: "ldap://192.168.56.13", username: testUSR, password: testPWD, codePage: .GB2312) + let ldap = try LDAP(url: testURL, username: testUSR, password: testPWD, codePage: .GB2312) print("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~") let sort = LDAP.sortingString(sortedBy: [("displayName", .DSC), ("initials", .ASC)]) print(sort) @@ -115,7 +118,7 @@ class PerfectLDAPTests: XCTestCase { func testAttributeMod () { do { - let ldap = try LDAP(url: "ldap://192.168.56.13", username: testUSR, password: testPWD, codePage: .GB2312) + let ldap = try LDAP(url: testURL, username: testUSR, password: testPWD, codePage: .GB2312) guard let rs = try ldap.search(base:"cn=users,dc=p,dc=com",filter: "(initials=RW)", scope:.SUBTREE) else { XCTFail("search failed") return @@ -146,7 +149,8 @@ class PerfectLDAPTests: XCTestCase { func testSASLDefaults () { do { - let ldap = try LDAP(url: "ldap://192.168.56.13") + + let ldap = try LDAP(url: testURL) let sasl = ldap.supportedSASL guard let gssapi = sasl[LDAP.AuthType.GSSAPI] else { @@ -179,6 +183,16 @@ class PerfectLDAPTests: XCTestCase { } } + func testSASLogin() { + do { + + let ldap = try LDAP(url: testURL) + try ldap.login(username: testUSR, password: testPWD, realm: testRLM, auth: .GSSAPI) + }catch(let err) { + XCTFail("error: \(err)") + } + } + static var allTests : [(String, (PerfectLDAPTests) -> () throws -> Void)] { return [ ("testLogin", testLogin), @@ -187,7 +201,8 @@ class PerfectLDAPTests: XCTestCase { ("testSearchSync", testSearchSync), ("testAttributeMod", testAttributeMod), ("testServerSort", testServerSort), - ("testSASLDefaults", testSASLDefaults) + ("testSASLDefaults", testSASLDefaults), + ("testSASLogin", testSASLogin) ] } }