// AccntCmt.cpp : Implementation of CAddUser_AccntCommit #include "stdafx.h" #include "AU_Accnt.h" #include "AccntCmt.h" #include #include #include #include #include // ldap/adsi includes #include #include #include #include #include #include #include // POP3 Defines #define SZ_AUTH_ID_MD5_HASH _T("c395e20c-2236-4af7-b736-54fad07dc526") // Defines for account flags. #define PASSWD_NOCHANGE 0x01 #define PASSWD_CANCHANGE 0x02 #define PASSWD_MUSTCHANGE 0x04 #ifndef CHECK_HR #define CHECK_HR( x ) CHECK_HR_RET( x, _hr ); #define CHECK_HR_RET( x, y ) \ { \ HRESULT _hr = x; \ if( FAILED(_hr) ) \ { \ _ASSERT( !_T("CHECK_HR_RET() failed. calling TRACE() with HRESULT and statement") ); \ return y; \ } \ } #endif // CHECK_HRhr; // **************************************************************************** // CAddUser_AccntCommit // **************************************************************************** // ---------------------------------------------------------------------------- // WriteErrorResults() // ---------------------------------------------------------------------------- HRESULT CAddUser_AccntCommit::WriteErrorResults( IPropertyPagePropertyBag* pPPPBag ) { // ------------------------------------------------------------------------ // Write the the values to the property bag. // ------------------------------------------------------------------------ if( FAILED(WriteInt4 ( pPPPBag, PROP_ACCNT_ERROR_CODE_GUID_STRING, m_dwErrCode, FALSE )) ) { _ASSERT(FALSE); } if( FAILED(WriteString( pPPPBag, PROP_ACCNT_ERROR_STR_GUID_STRING, m_csErrStr, FALSE )) ) { _ASSERT(FALSE); } return S_OK; } // ---------------------------------------------------------------------------- // SetErrCode() // ---------------------------------------------------------------------------- void CAddUser_AccntCommit::SetErrCode( DWORD dwCode ) { m_dwErrCode |= dwCode; return; } // ---------------------------------------------------------------------------- // SetErrorResults() // ---------------------------------------------------------------------------- HRESULT CAddUser_AccntCommit::SetErrorResults( DWORD dwErrType, BOOL bPOP3 /* = FALSE */ ) { SetErrCode(dwErrType); CString csTemp; switch( dwErrType ) { case ERROR_CREATION: case ERROR_PROPERTIES: { m_csErrStr.LoadString(IDS_ERROR_CREATING_USER); break; } case ERROR_MAILBOX: { csTemp.LoadString( IDS_ERROR_POP3MAILBOX); m_csErrStr += csTemp; break; } case ERROR_PASSWORD: { csTemp.LoadString(IDS_ERROR_PASSWORD); m_csErrStr += csTemp; break; } case ERROR_DUPE: { csTemp.LoadString(IDS_ERROR_DUPLICATE); m_csErrStr += csTemp; break; } case ERROR_MEMBERSHIPS: { csTemp.LoadString(IDS_ERROR_MEMBERSHIP); m_csErrStr += csTemp; break; } default : { m_dwErrCode = 0; m_csErrStr = _T(""); break; } } return S_OK; } // ---------------------------------------------------------------------------- // Commit() // ---------------------------------------------------------------------------- HRESULT CAddUser_AccntCommit::Commit( IDispatch* pdispPPPBag ) { HRESULT hr = S_OK; HRESULT hrAdmin = IsUserInGroup( DOMAIN_ALIAS_RID_ADMINS ); if( hrAdmin != S_OK ) { return E_ACCESSDENIED; } CComPtr spPPPBag = NULL; hr = pdispPPPBag->QueryInterface( __uuidof(IPropertyPagePropertyBag), (void**)&spPPPBag ); if ( !spPPPBag ) return hr; BOOL bRO = FALSE; BOOL bPOP3 = FALSE; ReadBool( spPPPBag, PROP_POP3_CREATE_MB_GUID_STRING, &bPOP3, &bRO ); // Reset the error conditions SetErrorResults( 0 ); // ------------------------------------------------------------------------ // Read in the values from the property bag. // ------------------------------------------------------------------------ CUserInfo cUInfo( spPPPBag, this ); // ------------------------------------------------------------------------ // Use those values to do something. // ------------------------------------------------------------------------ hr = cUInfo.CreateAccount(); if( FAILED(hr) ) { if( !(m_dwErrCode & (ERROR_PASSWORD | ERROR_MEMBERSHIPS)) ) { if ( HRESULT_CODE(hr) == ERROR_OBJECT_ALREADY_EXISTS ) { SetErrorResults(ERROR_DUPE); } else { SetErrorResults(ERROR_CREATION); } WriteErrorResults(spPPPBag); return E_FAIL; } } hr = cUInfo.CreateMailbox(); if( FAILED(hr) ) { SetErrorResults(ERROR_MAILBOX, bPOP3); } if( m_dwErrCode ) { hr = E_FAIL; WriteErrorResults(spPPPBag); } return hr; } // ---------------------------------------------------------------------------- // Revert() // ---------------------------------------------------------------------------- HRESULT CAddUser_AccntCommit::Revert() { NET_API_STATUS nApi; HRESULT hr = S_OK; CComPtr spUser = NULL; CComPtr spDelOps = NULL; // Remove the entry-point in the AD if we made one. if( m_csADName != _T("") ) { CHECK_HR( ::ADsGetObject( m_csADName, IID_IADs, (void**)&spUser ) ); // Delete the User hr = spUser->QueryInterface( IID_IADsDeleteOps, (void**)&spDelOps ); if( SUCCEEDED(hr) && spDelOps ) { hr = spDelOps->DeleteObject( 0 ); } } return hr; } // **************************************************************************** // CUserInfo // **************************************************************************** // ---------------------------------------------------------------------------- // CUserInfo() // // Constructor. // ---------------------------------------------------------------------------- CUserInfo::CUserInfo( IPropertyPagePropertyBag* pPPPBag, CAddUser_AccntCommit* pCmt ) { _ASSERT(pCmt); m_pCmt = pCmt; m_pPPPBag = pPPPBag; m_dwAccountOptions = 0; m_bPOP3 = FALSE; // Get domain controller name PDOMAIN_CONTROLLER_INFO pDCI = NULL; DWORD dwErr = DsGetDcName( NULL, NULL, NULL, NULL, DS_DIRECTORY_SERVICE_REQUIRED, &pDCI ); // cache the domain if( (dwErr == NO_ERROR) && pDCI ) { m_csDomainName = pDCI->DomainName; NetApiBufferFree( pDCI ); pDCI = NULL; } // convert from '.' separated names to Dc=xxx,DC=yyy,... format m_csFQDomainName = GetDomainPath( m_csDomainName ).c_str(); ReadBag( ); } // ---------------------------------------------------------------------------- // ReadBag() // // Read in the values from the property bag into the member variables. // ---------------------------------------------------------------------------- HRESULT CUserInfo::ReadBag() { HRESULT hr = S_OK; if ( !m_pPPPBag ) // Make sure we have the property bag to read from. return(E_FAIL); // If not, fail. BOOL bRO; // User Properties ReadString( m_pPPPBag, PROP_USEROU_GUID_STRING, m_csUserOU, &bRO ); ReadString( m_pPPPBag, PROP_USERNAME_GUID_STRING, m_csUserName, &bRO ); ReadString( m_pPPPBag, PROP_USER_CN, m_csUserCN, &bRO ); ReadString( m_pPPPBag, PROP_PASSWD_GUID_STRING, m_csPasswd, &bRO ); ReadInt4 ( m_pPPPBag, PROP_ACCOUNT_OPT_GUID_STRING, (LONG*)&m_dwAccountOptions, &bRO ); ReadString( m_pPPPBag, PROP_USERNAME_PRE2K_GUID_STRING, m_csUserNamePre2k, &bRO ); ReadString( m_pPPPBag, PROP_FIRSTNAME_GUID_STRING, m_csFirstName, &bRO ); ReadString( m_pPPPBag, PROP_LASTNAME_GUID_STRING, m_csLastName, &bRO ); ReadString( m_pPPPBag, PROP_TELEPHONE_GUID_STRING, m_csTelephone, &bRO ); ReadString( m_pPPPBag, PROP_OFFICE_GUID_STRING, m_csOffice, &bRO ); ReadString( m_pPPPBag, PROP_DESCRIPTION_GUID_STRING, m_csDesc, &bRO ); ReadString( m_pPPPBag, PROP_LOGON_DNS, m_csLogonDns, &bRO ); // Mailbox properties ReadBool ( m_pPPPBag, PROP_POP3_CREATE_MB_GUID_STRING, &m_bPOP3, &bRO ); ReadString( m_pPPPBag, PROP_EX_ALIAS_GUID_STRING, m_csEXAlias, &bRO ); ReadString( m_pPPPBag, PROP_EX_SERVER_GUID_STRING, m_csEXServer, &bRO ); ReadString( m_pPPPBag, PROP_EX_HOMESERVER_GUID_STRING, m_csEXHomeServer, &bRO ); ReadString( m_pPPPBag, PROP_EX_HOME_MDB_GUID_STRING, m_csEXHomeMDB, &bRO ); // Escape the User's Name m_csUserCN = EscapeString(((LPCTSTR)m_csUserCN+3), 2); m_csUserCN = _T("CN=") + m_csUserCN; // Let's figure out the fullname right here and fill in the m_csFullName variable. m_csFirstName.TrimLeft ( ); m_csFirstName.TrimRight( ); m_csLastName.TrimLeft ( ); m_csLastName.TrimRight ( ); m_csFullName.FormatMessage(IDS_FULLNAME_FORMAT_STR, (LPCTSTR)m_csFirstName, (LPCTSTR)m_csLastName); m_csFullName.TrimLeft ( ); m_csFullName.TrimRight ( ); return(hr); } // ---------------------------------------------------------------------------- // CreateAccount() // // Makes a new user account in the Active Directory. // ---------------------------------------------------------------------------- HRESULT CUserInfo::CreateAccount( ) { HRESULT hr = S_OK; BOOL bRO = TRUE; CComVariant vaTmpVal; CComBSTR bstrProp; CComPtr spADsContainer = NULL; // Bind to the container. CString csLdapUserOU = _T("LDAP://"); if( _tcsstr( (LPCTSTR)m_csUserOU, _T("LDAP://") ) ) { csLdapUserOU = m_csUserOU; } else { csLdapUserOU += m_csUserOU; } hr = ::ADsGetObject( (LPWSTR)(LPCWSTR)csLdapUserOU, IID_IADsContainer, (VOID**) &spADsContainer ); CHECK_HR(hr); // Create the user account. CComPtr spDisp = NULL; bstrProp = _T("user"); CComBSTR bstrValue = (LPCWSTR)m_csUserCN; hr = spADsContainer->Create( bstrProp, bstrValue, &spDisp ); CHECK_HR(hr); m_pCmt->m_csADName = _T("LDAP://"); m_pCmt->m_csADName += m_csUserCN; m_pCmt->m_csADName += _T(","); m_pCmt->m_csADName += (LPCTSTR)csLdapUserOU+7; // Use this new account and set it's properties (e.g. first name, home folder, etc.). CComQIPtr spADsUserObj(spDisp); if( !spADsUserObj ) { _ASSERT(FALSE); return E_FAIL; } TCHAR szTmp[MAX_PATH*4] = {0}; LdapToDCName( (LPCTSTR)csLdapUserOU, szTmp, (MAX_PATH*4) ); CString csUserPrincName = m_csUserName; csUserPrincName += _T("@"); csUserPrincName += szTmp; // Username: vaTmpVal.Clear(); vaTmpVal = ((LPCWSTR)csUserPrincName); bstrProp = _T("userPrincipalName"); CHECK_HR( spADsUserObj->Put( bstrProp, vaTmpVal ) ); // Pre-Win2000 username: vaTmpVal.Clear(); vaTmpVal = ((LPCWSTR)m_csUserNamePre2k); bstrProp = _T("sAMAccountName"); CHECK_HR( spADsUserObj->Put( bstrProp, vaTmpVal ) ); // First name: if( m_csFirstName.GetLength() ) { bstrProp = (LPCWSTR)m_csFirstName; CHECK_HR( spADsUserObj->put_FirstName( bstrProp ) ); } // Last name: if( m_csLastName.GetLength() ) { bstrProp = (LPCWSTR)m_csLastName; CHECK_HR( spADsUserObj->put_LastName( bstrProp ) ); } if( m_csFullName.GetLength() ) { // Full name: bstrProp = (LPCWSTR)m_csFullName; CHECK_HR( spADsUserObj->put_FullName( bstrProp ) ); // Display Name vaTmpVal.Clear(); vaTmpVal = (LPCWSTR)m_csFullName; bstrProp = _T("displayName"); CHECK_HR( spADsUserObj->Put( bstrProp, vaTmpVal ) ); } // Telephone number if( _tcslen((LPCTSTR)m_csTelephone) ) { vaTmpVal.Clear(); vaTmpVal = (LPCWSTR)m_csTelephone; bstrProp = _T("telephoneNumber"); CHECK_HR( spADsUserObj->Put( bstrProp, vaTmpVal ) ); } // Office Location if( _tcslen((LPCTSTR)m_csOffice) ) { vaTmpVal.Clear(); vaTmpVal = (LPCWSTR)m_csOffice; bstrProp = _T("physicalDeliveryOfficeName"); CHECK_HR( spADsUserObj->Put( bstrProp, vaTmpVal ) ); } // Commit this information to the AD. CHECK_HR( spADsUserObj->SetInfo() ); // Password expired? vaTmpVal.Clear(); vaTmpVal = (m_dwAccountOptions & PASSWD_MUSTCHANGE) ? (INT) 0 : (INT) -1; bstrProp = _T("pwdLastSet"); CHECK_HR( spADsUserObj->Put( bstrProp, vaTmpVal ) ); // Account disabled? vaTmpVal.Clear(); bstrProp = _T("userAccountControl"); CHECK_HR( spADsUserObj->Get( bstrProp, &vaTmpVal ) ); vaTmpVal.lVal &= ~UF_PASSWD_NOTREQD; // Make passwd required. vaTmpVal.lVal &= ~UF_ACCOUNTDISABLE; // Do not disable the account. bstrProp = _T("userAccountControl"); CHECK_HR( spADsUserObj->Put( bstrProp, vaTmpVal ) ); // Set the password. if( FAILED(SetPasswd()) ) { m_pCmt->SetErrorResults( ERROR_PASSWORD ); vaTmpVal.Clear(); bstrProp = _T("userAccountControl"); CHECK_HR( spADsUserObj->Get( bstrProp, &vaTmpVal ) ); vaTmpVal.lVal &= ~UF_PASSWD_NOTREQD; // Make passwd required. vaTmpVal.lVal |= UF_ACCOUNTDISABLE; // Disable the account. bstrProp = _T("userAccountControl"); CHECK_HR( spADsUserObj->Put( bstrProp, vaTmpVal ) ); // Commit this information to the AD. CHECK_HR( spADsUserObj->SetInfo() ); return E_FAIL; } // Commit this information to the AD. CHECK_HR( spADsUserObj->SetInfo() ); // Join to Domain Users if ( FAILED(JoinToDomainUsers()) ) { m_pCmt->SetErrorResults( ERROR_MEMBERSHIPS ); return E_FAIL; } return S_OK; } // ---------------------------------------------------------------------------- // CreateMailbox() // // Makes a new mailbox for the user. // ---------------------------------------------------------------------------- HRESULT CUserInfo::CreateMailbox() { HRESULT hr = S_OK; if( m_bPOP3 ) { hr = CreatePOP3Mailbox(); } return hr; } // ---------------------------------------------------------------------------- // CreatePOP3Mailbox() // // Makes a new (MS) POP3 mailbox for the user. // ---------------------------------------------------------------------------- HRESULT CUserInfo::CreatePOP3Mailbox() { HRESULT hr = S_OK; CComPtr spConfig = NULL; CComPtr spMethods = NULL; CComPtr spAuth = NULL; CComPtr spDomains = NULL; CComPtr spDomain = NULL; CComPtr spUsers = NULL; // Open our Pop3 Admin Interface hr = CoCreateInstance(__uuidof(P3Config), NULL, CLSCTX_ALL, __uuidof(IP3Config), (LPVOID*)&spConfig); if( SUCCEEDED(hr) ) { // Get the Domains hr = spConfig->get_Domains( &spDomains ); } if( SUCCEEDED(hr) ) { // Get the first domain CComVariant cVar; cVar = 1; hr = spDomains->get_Item( cVar, &spDomain ); } if( SUCCEEDED(hr) ) { hr = spDomain->get_Users( &spUsers ); } if ( SUCCEEDED(hr) ) { hr = spConfig->get_Authentication( &spMethods ); } CComVariant var; if ( SUCCEEDED(hr) ) { hr = spMethods->get_CurrentAuthMethod( &var ); } if ( SUCCEEDED(hr) ) { hr = spMethods->get_Item( var, &spAuth ); } CComBSTR bstrID; if( SUCCEEDED(hr) ) { hr = spAuth->get_ID( &bstrID ); } if( SUCCEEDED(hr) ) { CComBSTR bstrName = (LPCTSTR)m_csEXAlias; if( _tcsicmp(bstrID, SZ_AUTH_ID_MD5_HASH) == 0 ) { CComBSTR bstrPassword = m_csPasswd; hr = spUsers->AddEx( bstrName, bstrPassword ); SecureZeroMemory( (LPOLESTR)bstrPassword.m_str, sizeof(OLECHAR)*bstrPassword.Length() ); } else { hr = spUsers->Add( bstrName ); } } return hr; } // ---------------------------------------------------------------------------- // SetPasswd() // ---------------------------------------------------------------------------- HRESULT CUserInfo::SetPasswd() { HRESULT hr = S_OK; TCHAR *szPath = NULL; TCHAR *szTok = NULL; CString csUser = _T("LDAP://"); if ( _tcsstr((LPCTSTR)m_csUserOU, _T("LDAP://")) ) { csUser += m_csUserCN; csUser += _T(","); csUser += (LPCTSTR)m_csUserOU+7; } else { csUser += m_csUserCN; csUser += _T(","); csUser += (LPCTSTR)m_csUserOU; } // Now csUser is something like "WinNT://test.microsoft.com/JohnDoe,user" CComPtr spDS = NULL; hr = ::ADsGetObject( (LPCWSTR)csUser, IID_IADsUser, (void**)&spDS ); if ( FAILED(hr) ) return(hr); // Set the password. if ( _tcslen((LPCTSTR)m_csPasswd) ) // Only if there IS a passwd! { CComBSTR bszPasswd = (LPCWSTR) m_csPasswd; hr = spDS->SetPassword(bszPasswd); if ( FAILED(hr) ) return(hr); } // Allow change? if ( m_dwAccountOptions & PASSWD_NOCHANGE ) { // Get the current ACL. CComBSTR bstrName = csUser; PACL pDACL = NULL; PSECURITY_DESCRIPTOR pSD = NULL; hr = GetSDForDsObjectPath( bstrName, &pDACL, &pSD); if( hr != S_OK ) return hr; // build SID's for Self and World. PSID pSidSelf = NULL; SID_IDENTIFIER_AUTHORITY NtAuth = SECURITY_NT_AUTHORITY; if( !AllocateAndInitializeSid(&NtAuth, 1, SECURITY_PRINCIPAL_SELF_RID, 0, 0, 0, 0, 0, 0, 0, &pSidSelf) ) { return HRESULT_FROM_WIN32(GetLastError()); } PSID pSidWorld; SID_IDENTIFIER_AUTHORITY WorldAuth = SECURITY_WORLD_SID_AUTHORITY; if( !AllocateAndInitializeSid(&WorldAuth, 1, SECURITY_WORLD_RID, 0, 0, 0, 0, 0, 0, 0, &pSidWorld) ) { return HRESULT_FROM_WIN32(GetLastError()); } // initialize the entries (DENY ACE's) EXPLICIT_ACCESS rgAccessEntry[2] = {0}; OBJECTS_AND_SID rgObjectsAndSid[2] = {0}; rgAccessEntry[0].grfAccessPermissions = ACTRL_DS_CONTROL_ACCESS; rgAccessEntry[0].grfAccessMode = DENY_ACCESS; rgAccessEntry[0].grfInheritance = NO_INHERITANCE; rgAccessEntry[1].grfAccessPermissions = ACTRL_DS_CONTROL_ACCESS; rgAccessEntry[1].grfAccessMode = DENY_ACCESS; rgAccessEntry[1].grfInheritance = NO_INHERITANCE; // build the trustee structs for change password GUID UserChangePasswordGUID = { 0xab721a53, 0x1e2f, 0x11d0, { 0x98, 0x19, 0x00, 0xaa, 0x00, 0x40, 0x52, 0x9b}}; BuildTrusteeWithObjectsAndSid( &(rgAccessEntry[0].Trustee), &(rgObjectsAndSid[0]), &UserChangePasswordGUID, NULL, pSidSelf ); BuildTrusteeWithObjectsAndSid( &(rgAccessEntry[1].Trustee), &(rgObjectsAndSid[1]), &UserChangePasswordGUID, NULL, pSidWorld ); // Build the new DACL PACL pNewDACL = NULL; DWORD dwErr = ::SetEntriesInAcl(2, rgAccessEntry, pDACL, &pNewDACL); if( dwErr != ERROR_SUCCESS ) return HRESULT_FROM_WIN32(dwErr); // Set the new DACL hr = SetDaclForDsObjectPath( bstrName, pNewDACL ); if( hr != S_OK ) return hr; LocalFree(pSD); } // SetInfo only if we actually changed anything. if ( ( _tcslen((LPCTSTR)m_csPasswd) ) || // Did we mess with the passwd? ( m_dwAccountOptions & PASSWD_NOCHANGE ) ) // Did we make it unable to change? { hr = spDS->SetInfo(); // If either, then set the new info. } return(hr); } // ---------------------------------------------------------------------------- // JoinToGroup() // ---------------------------------------------------------------------------- HRESULT CUserInfo::JoinToDomainUsers() { USES_CONVERSION; HRESULT hr = S_OK; CString csGroupName; tstring strDomain; tstring strUser; // Get Domain SID USER_MODALS_INFO_2 *pUserModalsInfo2; NET_API_STATUS status = ::NetUserModalsGet( NULL, 2, (LPBYTE *)&pUserModalsInfo2 ); if ( (status != ERROR_SUCCESS) || (pUserModalsInfo2 == NULL) ) { return E_FAIL; } PSID pSIDDomain = pUserModalsInfo2->usrmod2_domain_id; // copy Domain RIDs UCHAR nSubAuthorities = *::GetSidSubAuthorityCount(pSIDDomain); DWORD adwSubAuthority[8]; for ( UCHAR index = 0; index < nSubAuthorities; index++ ) { adwSubAuthority[index] = *::GetSidSubAuthority(pSIDDomain, index); } adwSubAuthority[nSubAuthorities++] = DOMAIN_GROUP_RID_USERS; // finally, append the RID we want. SID_IDENTIFIER_AUTHORITY SIDAuthNT = SECURITY_NT_AUTHORITY; PSID pSid = NULL; if ( !::AllocateAndInitializeSid(&SIDAuthNT, nSubAuthorities, adwSubAuthority[0], adwSubAuthority[1], adwSubAuthority[2], adwSubAuthority[3], adwSubAuthority[4], adwSubAuthority[5], adwSubAuthority[6], adwSubAuthority[7], &pSid) ) { ::NetApiBufferFree(pUserModalsInfo2); return E_FAIL; } ::NetApiBufferFree(pUserModalsInfo2); // The builtin group names are looked up on the DC TCHAR szDomain[MAX_PATH]; TCHAR szName[MAX_PATH]; DWORD cbDomain = MAX_PATH; DWORD cbName = MAX_PATH; SID_NAME_USE peUse; if( !::LookupAccountSid( NULL, pSid, szName, &cbName, szDomain, &cbDomain, &peUse ) ) { FreeSid(pSid); return E_FAIL; } FreeSid( pSid ); // Find the Group Name CString csTmp; tstring strTmp = _T("LDAP://"); strTmp += m_csFQDomainName; hr = FindADsObject(strTmp.c_str(), szName, _T("(name=%1)"), csTmp, 1, TRUE); CHECK_HR( hr ); strTmp = _T("LDAP://"); strTmp += csTmp; // Open the group CComPtr spGroup = NULL; hr = ::ADsGetObject( strTmp.c_str(), IID_IADsGroup, (void**)&spGroup ); if( SUCCEEDED(hr) ) { // Add the User CComBSTR bstrName = (LPCTSTR)m_csUserOU; hr = spGroup->Add( bstrName ); if ( HRESULT_CODE(hr) == ERROR_OBJECT_ALREADY_EXISTS ) { hr = S_FALSE; } } return hr; }