/////////////////////////////////////////////////////////////////////////////// // // FILE // // ldapcxn.cpp // // SYNOPSIS // // This file defines the class LDAPConnection. // // MODIFICATION HISTORY // // 05/08/1998 Original version. // 09/16/1998 Perform access check after bind. // 03/23/1999 Set timeout for connect. // 04/14/1999 Specify domain and server when opening a connection. // 07/09/1999 Disable auto reconnect. // 09/14/1999 Always specify timeout for LDAP searches. // 01/25/2000 Encrypt LDAP connections. // Pass server name (not domain) to ldap_initW. // /////////////////////////////////////////////////////////////////////////////// #include #include #include #define SECURITY_WIN32 #include #include #include // Search timeout. LDAP_TIMEVAL LDAPConnection::SEARCH_TIMEOUT = { 10, 0 }; namespace { // Timeout for LDAP connects. LDAP_TIMEVAL CONNECT_TIMEOUT = { 15, 0 }; // RDN for the dummy object used for access checks. const WCHAR DUMMY_OBJECT[] = L"CN=RAS and IAS Servers Access Check,CN=System,"; /* Credentials for Kerberos-only bind. SEC_WINNT_AUTH_IDENTITY_EXW BIND_CREDENTIALS = { SEC_WINNT_AUTH_IDENTITY_VERSION, sizeof(SEC_WINNT_AUTH_IDENTITY_EXW), NULL, 0, NULL, 0, NULL, 0, SEC_WINNT_AUTH_IDENTITY_UNICODE, L"Kerberos", 8 }; */ } void LDAPConnection::Release() throw () { if (!InterlockedDecrement(&refCount)) { delete this; } } DWORD LDAPConnection::createInstance( PCWSTR domain, PCWSTR server, LDAPConnection** cxn ) throw () { DWORD status; ULONG opt; // Check the input parameters. if (cxn == NULL) { return ERROR_INVALID_PARAMETER; } // Initialize the connection. LDAP* ld = ldap_initW( const_cast(server), LDAP_PORT ); if (ld == NULL) { return LdapMapErrorToWin32(LdapGetLastError()); } // Set the domain name. status = ldap_set_optionW(ld, LDAP_OPT_DNSDOMAIN_NAME, &domain); if (status != LDAP_SUCCESS) { IASTraceLdapFailure("ldap_set_optionW", status, ld); ldap_unbind(ld); return LdapMapErrorToWin32(status); } opt = PtrToUlong(LDAP_OPT_ON); status = ldap_set_optionW(ld, LDAP_OPT_AREC_EXCLUSIVE, &opt); if (status != LDAP_SUCCESS) { IASTraceLdapFailure("ldap_set_optionW", status, ld); ldap_unbind(ld); return LdapMapErrorToWin32(status); } // Turn on encryption. opt = PtrToUlong(LDAP_OPT_ON); status = ldap_set_optionW(ld, LDAP_OPT_ENCRYPT, &opt); if (status != LDAP_SUCCESS) { IASTraceLdapFailure("ldap_set_optionW", status, ld); ldap_unbind(ld); return LdapMapErrorToWin32(status); } // Connect to the server ... status = ldap_connect(ld, &CONNECT_TIMEOUT); if (status == LDAP_SUCCESS) { // ... and bind the connection. status = ldap_bind_sW(ld, NULL, NULL, LDAP_AUTH_NEGOTIATE); if (status != LDAP_SUCCESS) { IASTraceLdapFailure("ldap_bind_s", status, ld); ldap_unbind(ld); return LdapMapErrorToWin32(status); } } else { IASTraceLdapFailure("ldap_connect", status, ld); ldap_unbind(ld); return LdapMapErrorToWin32(status); } // Turn off automatic chasing of referrals. opt = PtrToUlong(LDAP_OPT_OFF); ldap_set_option(ld, LDAP_OPT_REFERRALS, &opt); // Turn off auto reconnect of bad connections. opt = PtrToUlong(LDAP_OPT_OFF); ldap_set_option(ld, LDAP_OPT_AUTO_RECONNECT, &opt); // Turn on ldap_conn_from_msg. opt = PtrToUlong(LDAP_OPT_ON); ldap_set_option(ld, LDAP_OPT_REF_DEREF_CONN_PER_MSG, &opt); // Create the LDAPConnection wrapper. LDAPConnection* newCxn = new (std::nothrow) LDAPConnection(ld); if (newCxn == NULL) { ldap_unbind(ld); return ERROR_NOT_ENOUGH_MEMORY; } // Read the RootDSE. status = newCxn->readRootDSE(); // Check access permissions. if (status == NO_ERROR) { status = newCxn->checkAccess(); } // Process the result. if (status == NO_ERROR) { *cxn = newCxn; } else { newCxn->Release(); } return status; } ////////// // // NOTE: This doesn't have to be serialized since it's only called from // within createInstance. // ////////// DWORD LDAPConnection::checkAccess() throw () { // Allocate a temporary buffer for the DN. size_t len = wcslen(base) * sizeof(WCHAR) + sizeof(DUMMY_OBJECT); PWSTR dn = (PWSTR)_alloca(len); // Construct the DN of the dummy object. memcpy(dn, DUMMY_OBJECT, sizeof(DUMMY_OBJECT)); wcscat(dn, base); // Try to read the dummy object. PWCHAR attrs[] = { L"CN", NULL }; LDAPMessage* res = NULL; ULONG ldapError = ldap_search_ext_sW( connection, dn, LDAP_SCOPE_BASE, L"(objectclass=*)", attrs, TRUE, NULL, NULL, &SEARCH_TIMEOUT, 0, &res ); // We have two different error codes. if (ldapError == LDAP_SUCCESS) { ldapError = res->lm_returncode; } DWORD status = NO_ERROR; if (ldapError != LDAP_SUCCESS) { TraceFailure("ldap_search_ext_sW", ldapError); status = LdapMapErrorToWin32(ldapError); } else { // Get the first attribute from the first entry. BerElement* ptr; PWCHAR attr = ldap_first_attributeW( connection, ldap_first_entry(connection, res), &ptr ); // If we couldn't read any attributes, then we must not be a member // of the RAS and IAS Servers group. if (attr == NULL) { status = ERROR_ACCESS_DENIED; } } ldap_msgfree(res); return status; } ////////// // // NOTE: This doesn't have to be serialized since it's only called from // within createInstance. // ////////// DWORD LDAPConnection::readRootDSE() throw () { ////////// // Read the defaultNamingContext from the RootDSE. ////////// PWCHAR attrs[] = { LDAP_OPATT_DEFAULT_NAMING_CONTEXT_W, NULL }; LDAPMessage* res = NULL; ULONG ldapError = ldap_search_ext_sW( connection, L"", LDAP_SCOPE_BASE, L"(objectclass=*)", attrs, 0, NULL, NULL, &SEARCH_TIMEOUT, 0, &res ); // We have two different error codes. if (ldapError == LDAP_SUCCESS) { ldapError = res->lm_returncode; } if (ldapError != LDAP_SUCCESS) { TraceFailure("ldap_search_ext_sW", ldapError); ldap_msgfree(res); return LdapMapErrorToWin32(ldapError); } ////////// // The search succeeded, so get the attribute value. ////////// PWCHAR* vals = ldap_get_valuesW( connection, ldap_first_entry(connection, res), LDAP_OPATT_DEFAULT_NAMING_CONTEXT_W ); DWORD status; if (vals && *vals) { // We got something, so save the value. base = ias_wcsdup(*vals); status = base ? NO_ERROR : ERROR_NOT_ENOUGH_MEMORY; } else { // It's a mandatory attribute but we can't see it, so we must not have // sufficient permission. status = ERROR_ACCESS_DENIED; } ldap_value_freeW(vals); ldap_msgfree(res); return status; } void LDAPConnection::TraceFailure( PCSTR functionName, ULONG errorCode ) { IASTraceLdapFailure(functionName, errorCode, connection); } void IASTraceLdapFailure( PCSTR functionName, ULONG errorCode, LDAP* cxn ) { _ASSERT(functionName != NULL); IASTracePrintf("LDAP ERROR in %s. Code = %d", functionName, errorCode); PWCHAR errorString = NULL; ULONG result = ldap_get_optionW( cxn, LDAP_OPT_SERVER_ERROR, (void*) &errorString ); if (result == LDAP_SUCCESS) { IASTracePrintf("Extended error string: %S", errorString); // do what you want with the string here ldap_memfree(errorString); } }