LDAP SASL Binding C++ - c++

I'm currently implementing authorization mechanism on Linux against AD domain. I use for authorization the OpenLDAP library. Now I'm trying to perform the binding operation using ldap_sasl_bind_s function, and as the response from server my application is receiving the challenge but I'm not sure how to solve it. So I'm stuck with this:
berval creds; // User creds
berval *srv = NULL; // Server challenge
creds.bv_val = (char*)password.c_str();
creds.bv_len = password.length();
ret = ldap_sasl_bind_s(
ldapConnection,
username.c_str(),
"DIGEST-MD5",
&creds,
NULL,
NULL,
&srv
);
if((srv != NULL) && (ret == LDAP_SASL_BIND_IN_PROGRESS)) // If challenge has been received
{
// Challenge solving mechanism goes there.
ret = ldap_sasl_bind_s(
ldapConnection,
username.c_str(),
"DIGEST-MD5",
srv, // Not sure if it's the right place
NULL,
NULL,
NULL
);
if(ret != LDAP_SUCCESS) // Here I get 0x31 (LDAP_INVALID_CREDENTIALS)
{
ldap_unbind_ext(ldapConnection, NULL, NULL);
return false;
}
}

Ok, thanks to IBM Knowledge Center I figured how to bind credentials. Using simple auth mechanism we can do this by calling
ret = ldap_sasl_bind_s(
ldapConnection,
"username#example.com",
NULL, // Simple bind mechanism
&creds,
NULL,
NULL,
NULL
);

Related

Bad Request: message text is empty when sending get request via winapi to telegram bot

I'm trying to send message to telegram chat from bot using winapi and c++.
Here is my code
char szData[1024];
// initialize WinInet
HINTERNET hInternet = ::InternetOpen(TEXT("WinInet Test"), INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0);
if (hInternet != NULL)
{
// open HTTP session
HINTERNET hConnect = ::InternetConnect(hInternet, L"api.telegram.org", INTERNET_DEFAULT_HTTPS_PORT, NULL, NULL, INTERNET_SERVICE_HTTP, NULL, 1);
if (hConnect != NULL)
{
wstring request = L"/bot<bot_id>/sendMessage";
// open request
HINTERNET hRequest = ::HttpOpenRequest(hConnect, L"GET", (LPCWSTR)request.c_str(), NULL, NULL, 0, INTERNET_FLAG_KEEP_CONNECTION | INTERNET_FLAG_SECURE, 1);
if (hRequest != NULL)
{
// send request
const wchar_t* params = L"?chat_id=<chat_id>&text=test";
BOOL isSend = ::HttpSendRequest(hRequest, NULL, 0, (LPVOID)params, wcslen(params));
if (isSend)
{
for (;;)
{
// reading data
DWORD dwByteRead;
BOOL isRead = ::InternetReadFile(hRequest, szData, sizeof(szData) - 1, &dwByteRead);
// break cycle if error or end
if (isRead == FALSE || dwByteRead == 0)
break;
// saving result
szData[dwByteRead] = 0;
}
}
// close request
::InternetCloseHandle(hRequest);
}
// close session
::InternetCloseHandle(hConnect);
}
// close WinInet
::InternetCloseHandle(hInternet);
}
wstring answer = CharPToWstring(szData);
return answer;
But I've got {"ok":false,"error_code":400,"description":"Bad Request: message text is empty"} response. <chat_id> is id consisted of digits(12345678).
If I run this request in postman or in browser - then everything is ok.
I also tried to run this request using WinHttp* methods and result is the same.
What should I change in my request parameters to make it work?
There are a number of issues with this code:
You don't need to typecast the return value of wstring::c_str() to LPCWSTR (aka const wchar_t*), as it is already that type.
You can't send body data in a GET request. The Telegram Bot API expects body data to be sent in a POST request instead.
You are telling HttpSendRequest() to send body data from a wchar_t* UTF-16 string, but that is not the correct encoding that the server is expecting. You need to use a char* UTF-8 string instead.
You are not sending a Content-Type request header to tell the server what the format of the body data is. The API supports several different formats. In this case, since you are sending the data in application/x-www-form-urlencoded format, you need to add a Content-Type: application/x-www-form-urlencoded header to the request.
With all of that said, try this instead:
// initialize WinInet
HINTERNET hInternet = ::InternetOpenW(L"WinInet Test", INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0);
if (hInternet == NULL) ... // error handling
// open HTTP session
HINTERNET hConnect = ::InternetConnectW(hInternet, L"api.telegram.org", INTERNET_DEFAULT_HTTPS_PORT, NULL, NULL, INTERNET_SERVICE_HTTP, NULL, 1);
if (hConnect == NULL) ... // error handling
// open request
wstring wsResource = L"/bot<bot_id>/sendMessage";
HINTERNET hRequest = ::HttpOpenRequestW(hConnect, L"POST", wsResource.c_str(), NULL, NULL, 0, INTERNET_FLAG_KEEP_CONNECTION | INTERNET_FLAG_SECURE, 1);
if (hRequest == NULL) ... // error handling
// send request
string sBody = u8"chat_id=<chat_id>&text=test";
BOOL isSend = ::HttpSendRequestW(hRequest, L"Content-Type: application/x-www-form-urlencoded", -1L, sBody.c_str(), sBody.size());
if (!isSend) ... // error handling
string sReply;
char szData[1024];
DWORD dwByteRead;
while (::InternetReadFile(hRequest, szData, sizeof(szData), &dwByteRead) && dwByteRead != 0)
{
// saving result
sReply.append(szData, dwByteRead);
}
...
// use sReply as needed ...

Windows Login from C++ service

I am trying to create a Windows Service that is able to log on my user programatically into my local windows 10 pc the same as if i was typing directly in front of my keyboard. (I want to be able to initialize my session remotely)
I am using LogonUser and it returns properly but i will never see the user to appear as loged in in the Task Manager for example.
This is what i have tried now:
BSINFO(L"Login User...");
HANDLE sessToken = nullptr;
BOOL bRes = LogonUserW(
L"TestUser",
L".",
L"1234",
LOGON32_LOGON_INTERACTIVE,
LOGON32_PROVIDER_DEFAULT,
&sessToken);
if (bRes)
{
PROFILEINFO profInfo;
ZeroMemory(&profInfo,sizeof(PROFILEINFO));
profInfo.dwSize = sizeof(PROFILEINFO);
profInfo.lpUserName = L"TestUser";
profInfo.lpServerName = L"";
bRes = LoadUserProfileW(sessToken,&profInfo);
if (!bRes)
{
DWORD dwError = GetLastError();
BSERROR(L"Error LoadUserProfileW. %d '%ls'", dwError, GetLastErrorW(dwError).c_str());
}
}
else
{
DWORD dwError = GetLastError();
BSERROR(L"Error login user. %d '%ls'", dwError, GetLastErrorW(dwError).c_str());
}
Is it something possible to do?

Set the ACL specifying the worker processes that are allowed to receive I/O on the request queue

I'm playing with WinHTTP Server API 2.0 and I'm trying following steps(https://msdn.microsoft.com/en-us/library/windows/desktop/aa364672(v=vs.85).aspx):
Create the request queue and specify the name.
Configure the request queue using the HttpSetRequestQueueProperty function.
Query the request queue configuration parameters using the HttpQueryRequestQueueProperty function.
Create URL Groups and associates them with a request queue.
Set the ACL specifying the worker processes that are allowed to receive I/O on the request queue.
Call HttpWaitForDemandStart to delay the instantiation of worker processes until the first request arrives on the request queue.
Could anybody help with step:
Set the ACL specifying the worker processes that are allowed to receive I/O on the request queue
I'm not sure what does that really mean (I've never used ACL API), but I assume I need to call ::GetNamedSecurityInfo() at some point of time to modify it then:
if (NO_ERROR == ::HttpCreateRequestQueue(HTTPAPI_VERSION_2,
requestQueueName,
0,
HTTP_CREATE_REQUEST_QUEUE_FLAG_CONTROLLER,
&m_requestQueue))
// setup queue
if (NO_ERROR == ::HttpCreateUrlGroup(m_sessionId, &m_groupId, 0))
{
HTTP_BINDING_INFO bindingInfo{ 1, m_requestQueue };
if (NO_ERROR == ::HttpSetUrlGroupProperty(m_groupId,
HttpServerBindingProperty,
&bindingInfo,
sizeof(bindingInfo)))
{
m_localUrl = (NO_ERROR == (::HttpAddUrlToUrlGroup(m_groupId, localUrl.c_str(), 0, 0)));
m_globalUrl = (NO_ERROR == (::HttpAddUrlToUrlGroup(m_groupId, globalUrl.c_str(), 0, 0)));
PACL pacl = NULL;
PSECURITY_DESCRIPTOR securityDescriptor = NULL;
DWORD result = ::GetNamedSecurityInfo(requestQueueName,
SE_KERNEL_OBJECT,
SACL_SECURITY_INFORMATION,
NULL,
NULL,
NULL,
&pacl,
&securityDescriptor);
// it (result != 0) fails when passing various SE_OBJECT_TYPEs
}
}
Set the ACL specifying the worker processes that are allowed to
receive I/O on the request queue
look for else one note:
The named request queue is created with the HttpCreateRequestQueue
function. When the request queue is created, the application specifies
the ACL in the pSecurityAttribute parameter. The ACL, which can only
be set when the request queue is created, allows worker processes to
open the request queue, receive requests, and send responses. By
default, processes are not allowed to open a request queue unless they
have been granted permission in the ACL. Applications do not require
administrative privileges to create the request queue.
so really you can (but not must, this is optional) create and initialize some security descriptor and pass it to HttpCreateRequestQueue function via In_opt_ PSECURITY_ATTRIBUTES pSecurityAttributes - here absolute nothing special, the SECURITY_ATTRIBUTES used in any kernel object create api. say CreateEvent for example (here first parameter).
how initialize it, for whom grant access - this is already open question - the border case - allow this for all:
ULONG cb = MAX_SID_SIZE;
PSID UntrustedLabelSid = (PSID)alloca(MAX_SID_SIZE);
if (CreateWellKnownSid(WinUntrustedLabelSid, 0, UntrustedLabelSid, &cb))
{
PACL Sacl = (PACL)alloca(cb += sizeof(ACL) + sizeof(ACE_HEADER) + sizeof(ACCESS_MASK));
InitializeAcl(Sacl, cb, ACL_REVISION);
if (AddMandatoryAce(Sacl, ACL_REVISION, 0, 0, UntrustedLabelSid))
{
SECURITY_DESCRIPTOR sd;
InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION);
SetSecurityDescriptorDacl(&sd, TRUE, NULL, FALSE);
SetSecurityDescriptorSacl(&sd, TRUE, Sacl, FALSE);
SECURITY_ATTRIBUTES sa = { sizeof(sa), &sd, FALSE };
if (NO_ERROR == HttpCreateRequestQueue(HTTPAPI_VERSION_2,
requestQueueName,
&sa,
HTTP_CREATE_REQUEST_QUEUE_FLAG_CONTROLLER,
&m_requestQueue))
{
}
}
}
as alternative, we can use and string-format security descriptor and then convert it with ConvertStringSecurityDescriptorToSecurityDescriptor for example:
SECURITY_ATTRIBUTES sa = { sizeof(sa), 0, FALSE };
ULONG dwError;
if (ConvertStringSecurityDescriptorToSecurityDescriptorW(
L"D:NO_ACCESS_CONTROLS:(ML;;;;;LW)",
SDDL_REVISION_1, &sa.lpSecurityDescriptor, 0))
{
dwError = HttpCreateRequestQueue(HTTPAPI_VERSION_2,
requestQueueName,
&sa,
HTTP_CREATE_REQUEST_QUEUE_FLAG_CONTROLLER,
&m_requestQueue);
LocalFree(sa.lpSecurityDescriptor);
}
else
{
dwError = GetLastError();
}
here "D:NO_ACCESS_CONTROLS:(ML;;;;;LW)" allow all access to all - NO_ACCESS_CONTROLS and LW - LowLabel. (not untrusted as in first example)
another variant (only for example) use say next string:
"D:(A;;GA;;;SY)(A;;GA;;;BA)(A;;GRGX;;;BU)(A;;GRGX;;;AC)S:(ML;;;;;LW)"
here we allow GENERIC_ALL (GA) to System (SY) and Administrators (BA) and GENERIC_READ|GENERIC_EXECUTE for Users (BU) and ALL APPLICATION PACKAGES (AC)

How to copy privileges from one primary user token to another?

My goal is to copy privileges from one primary user token to another before I start a user mode process with the destination token. I created a sample pseudo code to illustrate what I need to accomplish.
The following will be run from a local system service:
//dwSessionId = user session ID to run process in
HANDLE hToken1 = NULL; //Source user token
WTSQueryUserToken(dwSessionId, &hToken1);
HANDLE hSelfToken = NULL; //User token for system service
HANDLE hToken2 = NULL; //Adjusted self-token
OpenProcessToken(::GetCurrentProcess(), TOKEN_ALL_ACCESS, &hSelfToken);
DuplicateTokenEx(hSelfToken, TOKEN_ASSIGN_PRIMARY | TOKEN_ALL_ACCESS,
NULL, SecurityIdentification, TokenPrimary, &hToken2);
//Specify user session to run in
SetTokenInformation(hToken2, TokenSessionId, &dwSessionId, sizeof(dwSessionId));
//Now I need to set privileges in 'hToken2' as they are in 'hToken1'
...
//Then use 'hToken2' in CreateProcessAsUser() to start a process
Any idea how to copy privileges from hToken1 to hToken2?
Use the following code to obtain hToken2 from hToken, and then use it in CreateProcessAsUser. There is no need to use hSelfToken, actually.
HANDLE GetAdjustedToken(HANDLE hSrcToken)
{
TOKEN_LINKED_TOKEN admin = {};
HANDLE hTarToken = 0;
DWORD dw = 0;
if (GetTokenInformation(hSrcToken, (TOKEN_INFORMATION_CLASS)TokenLinkedToken, &admin, sizeof(TOKEN_LINKED_TOKEN), &dw))
{
hTarToken = admin.LinkedToken;
}
else
{
DuplicateTokenEx(hSrcToken, MAXIMUM_ALLOWED, NULL, SecurityIdentification, TokenPrimary, &hTarToken);
}
return hTarToken;
}
And if you want to create the new process at a low mandatory integrity level, see this MSDN article.
First you have to call GetTokenInformation using TokenPrivileges class for your original token to obtain TOKEN_PRIVILEGES structure. Then call AdjustTokenPrivileges with it to update your cloned token. You will have to call GetTokenInformation twice - first one to obtain buffer length and second - to get actual data.
HANDLE hSelfToken = NULL; //User token for system service
HANDLE hToken2 = NULL; //Adjusted self-token
OpenProcessToken(::GetCurrentProcess(), TOKEN_ALL_ACCESS, &hSelfToken);
DuplicateTokenEx(hSelfToken, TOKEN_ASSIGN_PRIMARY | TOKEN_ALL_ACCESS,
NULL, SecurityIdentification, TokenPrimary, &hToken2);
PTOKEN_PRIVILEGES pPriv = NULL;
DWORD dwLen = 0;
GetTokenInformation(hSelfToken, TokenPrivileges, (LPVOID)pPriv, 0, &dwLen);
if (GetLastError() != ERROR_INSUFFICIENT_BUFFER)
return(0);
pPriv = (PTOKEN_PRIVILEGES)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, dwLen);
if (!GetTokenInformation(hSelfToken, TokenPrivileges, (LPVOID)pPriv, dwLen, &dwLen))
return(0);
AdjustTokenPrivileges(hToken2, FALSE, pPriv, dwLen, NULL, NULL);
OK. I might have gotten something close to what I needed. It's not really copying privileges, but for the purpose of "stripping out" privileges I can create a restricted token that will have almost all privileges removed (except SeChangeNotifyPrivilege and SeSystemtimePrivilege.)
//Call the following instead of DuplicateTokenEx() in my example above
CreateRestrictedToken(hSelfToken, DISABLE_MAX_PRIVILEGE,
0, NULL, 0, NULL, 0, NULL,
&hToken2);
FYI: Just a curious fact. For some reason I was not able to remove the SeSystemtimePrivilege from the token. Not with this method, nor using AdjustTokenPrivileges with its attribute set to SE_PRIVILEGE_REMOVED. All other privileges could be removed just fine, except this one. So if anyone have any idea why, I'd be glad to learn it?

SCHANNEL TLS Server side cannot CertFindCertificateInStore

I am adding TLS encryption to a server side application. I am using the Schannel API to add the TLS. I am having a problem with CertFindCertificateInStore. It does not ever find the certificate that I am searching for. As criteria for the search I am using the name of the certificate. I have spent many hours on this now and do not understand why it is not working. Any help would be immensely appreciated. The function I am using this in is found below. Thanks,
int ServerCreateCredentials() {
//- get the certificate store
HCERTSTORE myCertStore = NULL;
myCertStore = CertOpenStore(
CERT_STORE_PROV_SYSTEM,
X509_ASN_ENCODING,
NULL,
CERT_SYSTEM_STORE_LOCAL_MACHINE,
L"My");
// check for the failure to find the appropriate store
if (myCertStore == NULL) {
return 1;
}
// find the certificate in the store
m_CertificateContext = CertFindCertificateInStore(
myCertStore,
X509_ASN_ENCODING,
0,
CERT_FIND_SUBJECT_STR_A,
(LPVOID) CertificateName,
NULL);
if (m_CertificateContext == NULL) {
// try unicode
m_CertificateContext = CertFindCertificateInStore(
myCertStore,
X509_ASN_ENCODING,
0,
CERT_FIND_SUBJECT_STR_W,
CertificateName,
NULL);
if (m_CertificateContext == NULL ) {
// free the store
CertCloseStore(myCertStore, CERT_CLOSE_STORE_CHECK_FLAG);
return 2;
}
}
TimeStamp life;
// get the credentials
SCHANNEL_CRED SchannelCredentials;
ZeroMemory(&SchannelCredentials, sizeof(SchannelCredentials));
SchannelCredentials.dwVersion = SCHANNEL_CRED_VERSION;
SchannelCredentials.cCreds = 1; // number of credentials
SchannelCredentials.paCred = &m_CertificateContext; // credentials
SchannelCredentials.hRootStore = myCertStore; // certificate store location
SchannelCredentials.dwMinimumCipherStrength = 80; // minimum encryption allowed
SchannelCredentials.grbitEnabledProtocols = 0; // let the dll decide
SchannelCredentials.dwFlags = SCH_CRED_AUTO_CRED_VALIDATION
| SCH_CRED_NO_SERVERNAME_CHECK
| SCH_CRED_REVOCATION_CHECK_CHAIN;
DWORD Status = SEC_E_OK;
Status = m_securityFunctionTable.AcquireCredentialsHandle(
NULL,
UNISP_NAME,
SECPKG_CRED_INBOUND,
NULL,
&SchannelCredentials,
NULL,
NULL,
&m_credentials,
&life);
// at this point we should be good
// free the store
CertCloseStore(myCertStore, CERT_CLOSE_STORE_CHECK_FLAG);
if (Status != SEC_E_OK) {
return 3;
}
return 0;
I have figured out that I was not searching on the correct parameters. You need to search based on the subject name and then it will work.