Restructure Azure AD related stuff
- Move responsibility for obtaining access tokens to clients - Add function for getting access tokens for AVD - Get correct server hostname during AVD setup - Add utility function for doing http requests
This commit is contained in:
parent
b8c2b6abd7
commit
d309fcd6e8
@ -741,7 +741,10 @@ find_feature(Pulse ${PULSE_FEATURE_TYPE} ${PULSE_FEATURE_PURPOSE} ${PULSE_FEATUR
|
||||
find_feature(Cups ${CUPS_FEATURE_TYPE} ${CUPS_FEATURE_PURPOSE} ${CUPS_FEATURE_DESCRIPTION})
|
||||
find_feature(PCSC ${PCSC_FEATURE_TYPE} ${PCSC_FEATURE_PURPOSE} ${PCSC_FEATURE_DESCRIPTION})
|
||||
|
||||
find_feature(CJSON ${CJSON_FEATURE_TYPE} ${CJSON_FEATURE_PURPOSE} ${CJSON_FEATURE_DESCRIPTION})
|
||||
option(WITH_AAD "Compile with support for Azure AD authentication" ON)
|
||||
if (WITH_AAD)
|
||||
find_package(CJSON REQUIRED)
|
||||
endif()
|
||||
|
||||
if (WITH_DSP_FFMPEG OR WITH_VIDEO_FFMPEG OR WITH_FFMPEG)
|
||||
set(FFMPEG_FEATURE_TYPE "REQUIRED" )
|
||||
|
@ -1139,9 +1139,11 @@ static BOOL sdl_client_new(freerdp* instance, rdpContext* context)
|
||||
instance->VerifyChangedCertificateEx = client_cli_verify_changed_certificate_ex;
|
||||
instance->LogonErrorInfo = sdl_logon_error_info;
|
||||
#ifdef WITH_WEBVIEW
|
||||
instance->GetAadAuthCode = sdl_webview_get_aad_auth_code;
|
||||
instance->GetRDSAADAccessToken = sdl_webview_get_rdsaad_access_token;
|
||||
instance->GetAVDAccessToken = sdl_webview_get_avd_access_token;
|
||||
#else
|
||||
instance->GetAadAuthCode = client_cli_get_aad_auth_code;
|
||||
instance->GetRDSAADAccessToken = client_cli_get_rdsaad_access_token;
|
||||
instance->GetAVDAccessToken = client_cli_get_avd_access_token;
|
||||
#endif
|
||||
/* TODO: Client display set up */
|
||||
|
||||
|
@ -66,35 +66,14 @@ class SchemeHandler : public QWebEngineUrlSchemeHandler
|
||||
std::string m_code;
|
||||
};
|
||||
|
||||
BOOL sdl_webview_get_aad_auth_code(freerdp* instance, const char* hostname, char** code,
|
||||
const char** client_id, const char** redirect_uri)
|
||||
static std::string sdl_webview_get_auth_code(QString url)
|
||||
{
|
||||
int argc = 1;
|
||||
std::string name = "FreeRDP WebView";
|
||||
|
||||
WINPR_ASSERT(instance);
|
||||
WINPR_ASSERT(hostname);
|
||||
WINPR_ASSERT(code);
|
||||
WINPR_ASSERT(client_id);
|
||||
WINPR_ASSERT(redirect_uri);
|
||||
|
||||
WINPR_UNUSED(instance);
|
||||
|
||||
*code = nullptr;
|
||||
*client_id = "5177bc73-fd99-4c77-a90c-76844c9b6999";
|
||||
*redirect_uri =
|
||||
"ms-appx-web%3a%2f%2fMicrosoft.AAD.BrokerPlugin%2f5177bc73-fd99-4c77-a90c-76844c9b6999";
|
||||
|
||||
auto url =
|
||||
QString("https://login.microsoftonline.com/common/oauth2/v2.0/authorize?client_id=") +
|
||||
QString(*client_id) +
|
||||
QString("&response_type=code&scope=ms-device-service%3A%2F%2Ftermsrv.wvd.microsoft.com%"
|
||||
"2Fname%2F") +
|
||||
QString(hostname) + QString("%2Fuser_impersonation&redirect_uri=") + QString(*redirect_uri);
|
||||
char* argv[] = { name.data() };
|
||||
|
||||
QWebEngineUrlScheme::registerScheme(QWebEngineUrlScheme("ms-appx-web"));
|
||||
|
||||
char* argv[] = { name.data() };
|
||||
QCoreApplication::setOrganizationName("QtExamples");
|
||||
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
|
||||
QApplication app(argc, argv);
|
||||
@ -107,12 +86,60 @@ BOOL sdl_webview_get_aad_auth_code(freerdp* instance, const char* hostname, char
|
||||
webview.show();
|
||||
|
||||
if (app.exec() != 0)
|
||||
return FALSE;
|
||||
return "";
|
||||
|
||||
auto val = handler.code();
|
||||
if (val.empty())
|
||||
return FALSE;
|
||||
*code = _strdup(val.c_str());
|
||||
|
||||
return (*code != nullptr) ? TRUE : FALSE;
|
||||
return handler.code();
|
||||
}
|
||||
|
||||
BOOL sdl_webview_get_rdsaad_access_token(freerdp* instance, const char* scope, const char* req_cnf,
|
||||
char** token)
|
||||
{
|
||||
WINPR_ASSERT(instance);
|
||||
WINPR_ASSERT(scope);
|
||||
WINPR_ASSERT(req_cnf);
|
||||
WINPR_ASSERT(token);
|
||||
|
||||
WINPR_UNUSED(instance);
|
||||
|
||||
std::string client_id = "5177bc73-fd99-4c77-a90c-76844c9b6999";
|
||||
std::string redirect_uri =
|
||||
"ms-appx-web%3a%2f%2fMicrosoft.AAD.BrokerPlugin%2f5177bc73-fd99-4c77-a90c-76844c9b6999";
|
||||
|
||||
*token = nullptr;
|
||||
|
||||
auto url = QString::fromStdString(
|
||||
"https://login.microsoftonline.com/common/oauth2/v2.0/authorize?client_id=" + client_id +
|
||||
"&response_type=code&scope=" + scope + "&redirect_uri=" + redirect_uri);
|
||||
|
||||
auto code = sdl_webview_get_auth_code(url);
|
||||
if (code.empty())
|
||||
return FALSE;
|
||||
|
||||
auto token_request = "grant_type=authorization_code&code=" + code + "&client_id=" + client_id +
|
||||
"&scope=" + scope + "&redirect_uri=" + redirect_uri +
|
||||
"&req_cnf=" + req_cnf;
|
||||
return client_common_get_access_token(instance, token_request.c_str(), token);
|
||||
}
|
||||
|
||||
BOOL sdl_webview_get_avd_access_token(freerdp* instance, char** token)
|
||||
{
|
||||
WINPR_ASSERT(token);
|
||||
|
||||
std::string client_id = "a85cf173-4192-42f8-81fa-777a763e6e2c";
|
||||
std::string redirect_uri =
|
||||
"ms-appx-web%3a%2f%2fMicrosoft.AAD.BrokerPlugin%2fa85cf173-4192-42f8-81fa-777a763e6e2c";
|
||||
std::string scope = "https%3A%2F%2Fwww.wvd.microsoft.com%2F.default";
|
||||
|
||||
*token = nullptr;
|
||||
|
||||
auto url = QString::fromStdString(
|
||||
"https://login.microsoftonline.com/common/oauth2/v2.0/authorize?client_id=" + client_id +
|
||||
"&response_type=code&scope=" + scope + "&redirect_uri=" + redirect_uri);
|
||||
auto code = sdl_webview_get_auth_code(url);
|
||||
if (code.empty())
|
||||
return FALSE;
|
||||
|
||||
auto token_request = "grant_type=authorization_code&code=" + code + "&client_id=" + client_id +
|
||||
"&scope=" + scope + "&redirect_uri=" + redirect_uri;
|
||||
return client_common_get_access_token(instance, token_request.c_str(), token);
|
||||
}
|
||||
|
@ -26,8 +26,9 @@ extern "C"
|
||||
{
|
||||
#endif
|
||||
|
||||
BOOL sdl_webview_get_aad_auth_code(freerdp* instance, const char* hostname, char** code,
|
||||
const char** client_id, const char** redirect_uri);
|
||||
BOOL sdl_webview_get_avd_access_token(freerdp* instance, char** token);
|
||||
BOOL sdl_webview_get_rdsaad_access_token(freerdp* instance, const char* scope,
|
||||
const char* req_cnf, char** token);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
@ -1976,7 +1976,8 @@ static BOOL xfreerdp_client_new(freerdp* instance, rdpContext* context)
|
||||
instance->PostDisconnect = xf_post_disconnect;
|
||||
instance->PostFinalDisconnect = xf_post_final_disconnect;
|
||||
instance->LogonErrorInfo = xf_logon_error_info;
|
||||
instance->GetAadAuthCode = client_cli_get_aad_auth_code;
|
||||
instance->GetRDSAADAccessToken = client_cli_get_rdsaad_access_token;
|
||||
instance->GetAVDAccessToken = client_cli_get_avd_access_token;
|
||||
PubSub_SubscribeTerminate(context->pubSub, xf_TerminateEventHandler);
|
||||
#ifdef WITH_XRENDER
|
||||
PubSub_SubscribeZoomingChange(context->pubSub, xf_ZoomingChangeEventHandler);
|
||||
|
@ -58,6 +58,11 @@
|
||||
#include <freerdp/gdi/video.h>
|
||||
#endif
|
||||
|
||||
#ifdef WITH_AAD
|
||||
#include <cjson/cJSON.h>
|
||||
#include <freerdp/utils/http.h>
|
||||
#endif
|
||||
|
||||
#include <freerdp/log.h>
|
||||
#define TAG CLIENT_TAG("common")
|
||||
|
||||
@ -935,49 +940,178 @@ BOOL client_cli_present_gateway_message(freerdp* instance, UINT32 type, BOOL isD
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
BOOL client_cli_get_aad_auth_code(freerdp* instance, const char* hostname, char** code,
|
||||
const char** client_id, const char** redirect_uri)
|
||||
static char* extract_authorization_code(char* url)
|
||||
{
|
||||
WINPR_ASSERT(url);
|
||||
|
||||
for (char* p = strchr(url, '?'); p++ != NULL; p = strchr(p, '&'))
|
||||
{
|
||||
if (strncmp(p, "code=", 5) != 0)
|
||||
continue;
|
||||
|
||||
char* end = NULL;
|
||||
p += 5;
|
||||
|
||||
end = strchr(p, '&');
|
||||
if (end)
|
||||
*end = 0;
|
||||
else
|
||||
end = strchr(p, '\0');
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
BOOL client_cli_get_rdsaad_access_token(freerdp* instance, const char* scope, const char* req_cnf,
|
||||
char** token)
|
||||
{
|
||||
size_t size = 0;
|
||||
char* url = NULL;
|
||||
char* token_request = NULL;
|
||||
const char* client_id = "a85cf173-4192-42f8-81fa-777a763e6e2c";
|
||||
const char* redirect_uri =
|
||||
"https%3A%2F%2Flogin.microsoftonline.com%2Fcommon%2Foauth2%2Fnativeclient";
|
||||
|
||||
WINPR_ASSERT(instance);
|
||||
WINPR_ASSERT(hostname);
|
||||
WINPR_ASSERT(code);
|
||||
WINPR_ASSERT(client_id);
|
||||
WINPR_ASSERT(redirect_uri);
|
||||
WINPR_ASSERT(scope);
|
||||
WINPR_ASSERT(req_cnf);
|
||||
WINPR_ASSERT(token);
|
||||
|
||||
*code = NULL;
|
||||
*client_id = "a85cf173-4192-42f8-81fa-777a763e6e2c";
|
||||
*redirect_uri = "https%3A%2F%2Flogin.microsoftonline.com%2Fcommon%2Foauth2%2Fnativeclient";
|
||||
*token = NULL;
|
||||
|
||||
printf("Browse to: https://login.microsoftonline.com/common/oauth2/v2.0/"
|
||||
"authorize?client_id=%s&response_type="
|
||||
"code&scope=ms-device-service%%3A%%2F%%2Ftermsrv.wvd.microsoft.com%%2Fname%%"
|
||||
"2F%s%%2Fuser_impersonation&redirect_uri=%s"
|
||||
"code&scope=%s&redirect_uri=%s"
|
||||
"\n",
|
||||
*client_id, hostname, *redirect_uri);
|
||||
client_id, scope, redirect_uri);
|
||||
printf("Paste redirect URL here: \n");
|
||||
|
||||
if (freerdp_interruptible_get_line(instance->context, &url, &size, stdin) < 0)
|
||||
return FALSE;
|
||||
|
||||
for (char* p = strchr(url, '?'); p++ != NULL; p = strchr(p, '&'))
|
||||
{
|
||||
if (strncmp(p, "code=", 5) == 0)
|
||||
{
|
||||
char* end = NULL;
|
||||
p += 5;
|
||||
char* code = extract_authorization_code(url);
|
||||
if (!code)
|
||||
goto cleanup;
|
||||
|
||||
end = strchr(p, '&');
|
||||
if (end)
|
||||
*end = 0;
|
||||
*code = strdup(p);
|
||||
}
|
||||
if (winpr_asprintf(&token_request, &size,
|
||||
"grant_type=authorization_code&code=%s&client_id=%s&scope=%s&redirect_uri=%"
|
||||
"s&req_cnf=%s",
|
||||
code, client_id, scope, redirect_uri, req_cnf) <= 0)
|
||||
goto cleanup;
|
||||
|
||||
client_common_get_access_token(instance, token_request, token);
|
||||
|
||||
cleanup:
|
||||
free(token_request);
|
||||
free(url);
|
||||
return (*token != NULL);
|
||||
}
|
||||
|
||||
BOOL client_cli_get_avd_access_token(freerdp* instance, char** token)
|
||||
{
|
||||
size_t size = 0;
|
||||
char* url = NULL;
|
||||
char* token_request = NULL;
|
||||
const char* client_id = "a85cf173-4192-42f8-81fa-777a763e6e2c";
|
||||
const char* redirect_uri =
|
||||
"https%3A%2F%2Flogin.microsoftonline.com%2Fcommon%2Foauth2%2Fnativeclient";
|
||||
const char* scope = "https%3A%2F%2Fwww.wvd.microsoft.com%2F.default";
|
||||
|
||||
WINPR_ASSERT(instance);
|
||||
WINPR_ASSERT(token);
|
||||
|
||||
*token = NULL;
|
||||
|
||||
printf("Browse to: https://login.microsoftonline.com/common/oauth2/v2.0/"
|
||||
"authorize?client_id=%s&response_type="
|
||||
"code&scope=%s&redirect_uri=%s"
|
||||
"\n",
|
||||
client_id, scope, redirect_uri);
|
||||
printf("Paste redirect URL here: \n");
|
||||
|
||||
if (freerdp_interruptible_get_line(instance->context, &url, &size, stdin) < 0)
|
||||
return FALSE;
|
||||
|
||||
char* code = extract_authorization_code(url);
|
||||
if (!code)
|
||||
goto cleanup;
|
||||
|
||||
if (winpr_asprintf(
|
||||
&token_request, &size,
|
||||
"grant_type=authorization_code&code=%s&client_id=%s&scope=%s&redirect_uri=%s", code,
|
||||
client_id, scope, redirect_uri) <= 0)
|
||||
goto cleanup;
|
||||
|
||||
client_common_get_access_token(instance, token_request, token);
|
||||
|
||||
cleanup:
|
||||
free(token_request);
|
||||
free(url);
|
||||
return (*token != NULL);
|
||||
}
|
||||
|
||||
BOOL client_common_get_access_token(freerdp* instance, const char* request, char** token)
|
||||
{
|
||||
#ifdef WITH_AAD
|
||||
WINPR_ASSERT(request);
|
||||
WINPR_ASSERT(token);
|
||||
|
||||
BOOL ret = FALSE;
|
||||
long resp_code = 0;
|
||||
BYTE* response = NULL;
|
||||
size_t response_length = 0;
|
||||
cJSON* json = NULL;
|
||||
cJSON* access_token_prop = NULL;
|
||||
const char* access_token_str = NULL;
|
||||
|
||||
if (!freerdp_http_request("https://login.microsoftonline.com/common/oauth2/v2.0/token", request,
|
||||
&resp_code, &response, &response_length))
|
||||
{
|
||||
WLog_ERR(TAG, "access token request failed");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
free(url);
|
||||
return (*code != NULL);
|
||||
if (resp_code != 200)
|
||||
{
|
||||
WLog_ERR(TAG, "Server unwilling to provide access token; returned status code %li",
|
||||
resp_code);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
json = cJSON_ParseWithLength((const char*)response, response_length);
|
||||
if (!json)
|
||||
{
|
||||
WLog_ERR(TAG, "Failed to parse access token response");
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
access_token_prop = cJSON_GetObjectItem(json, "access_token");
|
||||
if (!access_token_prop)
|
||||
{
|
||||
WLog_ERR(TAG, "Response has no \"access_token\" property");
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
access_token_str = cJSON_GetStringValue(access_token_prop);
|
||||
if (!access_token_str)
|
||||
{
|
||||
WLog_ERR(TAG, "Invalid value for \"access_token\"");
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
*token = _strdup(access_token_str);
|
||||
if (*token)
|
||||
ret = TRUE;
|
||||
|
||||
cleanup:
|
||||
cJSON_Delete(json);
|
||||
free(response);
|
||||
return ret;
|
||||
#else
|
||||
return FALSE;
|
||||
#endif
|
||||
}
|
||||
|
||||
BOOL client_auto_reconnect(freerdp* instance)
|
||||
|
@ -20,7 +20,7 @@
|
||||
#cmakedefine WITH_CUPS
|
||||
#cmakedefine WITH_JPEG
|
||||
#cmakedefine WITH_WIN8
|
||||
#cmakedefine WITH_CJSON
|
||||
#cmakedefine WITH_AAD
|
||||
#cmakedefine WITH_CAIRO
|
||||
#cmakedefine WITH_SWSCALE
|
||||
#cmakedefine WITH_RDPSND_DSOUND
|
||||
|
@ -170,9 +170,11 @@ extern "C"
|
||||
|
||||
FREERDP_API int client_cli_logon_error_info(freerdp* instance, UINT32 data, UINT32 type);
|
||||
|
||||
FREERDP_API BOOL client_cli_get_aad_auth_code(freerdp* instance, const char* hostname,
|
||||
char** code, const char** client_id,
|
||||
const char** redirect_uri);
|
||||
FREERDP_API BOOL client_cli_get_rdsaad_access_token(freerdp* instance, const char* scope,
|
||||
const char* req_cnf, char** token);
|
||||
FREERDP_API BOOL client_cli_get_avd_access_token(freerdp* instance, char** token);
|
||||
FREERDP_API BOOL client_common_get_access_token(freerdp* instance, const char* request,
|
||||
char** token);
|
||||
|
||||
FREERDP_API void
|
||||
freerdp_client_OnChannelConnectedEventHandler(void* context,
|
||||
|
@ -126,8 +126,9 @@ extern "C"
|
||||
char** domain, rdp_auth_reason reason);
|
||||
typedef BOOL (*pChooseSmartcard)(freerdp* instance, SmartcardCertInfo** cert_list, DWORD count,
|
||||
DWORD* choice, BOOL gateway);
|
||||
typedef BOOL (*pGetAadAuthCode)(freerdp* instance, const char* hostname, char** code,
|
||||
const char** client_id, const char** redirect_uri);
|
||||
typedef BOOL (*pGetRDSAADAccessToken)(freerdp* instance, const char* scope, const char* req_cnf,
|
||||
char** token);
|
||||
typedef BOOL (*pGetAVDAccessToken)(freerdp* instance, char** token);
|
||||
|
||||
/** @brief Callback used if user interaction is required to accept
|
||||
* an unknown certificate.
|
||||
@ -521,10 +522,13 @@ owned by rdpRdp */
|
||||
Callback for choosing a smartcard for logon.
|
||||
Used when multiple smartcards are available. Returns an index into a list
|
||||
of SmartcardCertInfo pointers */
|
||||
ALIGN64 pGetAadAuthCode GetAadAuthCode; /* (offset 71)
|
||||
Callback for obtaining an oauth2 authorization
|
||||
code for RDS AAD authentication */
|
||||
UINT64 paddingE[80 - 72]; /* 72 */
|
||||
ALIGN64 pGetRDSAADAccessToken GetRDSAADAccessToken; /* (offset 71)
|
||||
Callback for obtaining an oauth2 access token
|
||||
for RDS AAD authentication */
|
||||
ALIGN64 pGetAVDAccessToken GetAVDAccessToken; /* (offset 72)
|
||||
Callback for obtaining an oauth2 access token
|
||||
for Azure Virtual Desktop */
|
||||
UINT64 paddingE[80 - 73]; /* 73 */
|
||||
};
|
||||
|
||||
struct rdp_channel_handles
|
||||
|
28
include/freerdp/utils/http.h
Normal file
28
include/freerdp/utils/http.h
Normal file
@ -0,0 +1,28 @@
|
||||
/**
|
||||
* FreeRDP: A Remote Desktop Protocol Implementation
|
||||
* Smartcard Device Service Virtual Channel
|
||||
*
|
||||
* Copyright 2023 Isaac Klein <fifthdegree@protonmail.com>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef FREERDP_UTILS_HTTP_H
|
||||
#define FREERDP_UTILS_HTTP_H
|
||||
|
||||
#include <freerdp/api.h>
|
||||
|
||||
FREERDP_API BOOL freerdp_http_request(const char* url, const char* body, long* status_code,
|
||||
BYTE** response, size_t* response_length);
|
||||
|
||||
#endif /* FREERDP_UTILS_HTTP_H */
|
@ -219,7 +219,7 @@ if(FAAC_FOUND)
|
||||
include_directories(${FAAC_INCLUDE_DIRS})
|
||||
endif()
|
||||
|
||||
if(WITH_CJSON)
|
||||
if(WITH_AAD)
|
||||
freerdp_library_add(${CJSON_LIBRARIES})
|
||||
include_directories(${CJSON_INCLUDE_DIRS})
|
||||
endif()
|
||||
|
@ -25,23 +25,19 @@
|
||||
#include <freerdp/crypto/crypto.h>
|
||||
#include <freerdp/crypto/privatekey.h>
|
||||
#include "../crypto/privatekey.h"
|
||||
#include <freerdp/utils/http.h>
|
||||
|
||||
#ifdef WITH_CJSON
|
||||
#ifdef WITH_AAD
|
||||
#include <cjson/cJSON.h>
|
||||
#endif
|
||||
|
||||
#include <winpr/crypto.h>
|
||||
|
||||
#include <openssl/ssl.h>
|
||||
#include <openssl/bio.h>
|
||||
#include <openssl/evp.h>
|
||||
#include <openssl/err.h>
|
||||
|
||||
#include "transport.h"
|
||||
|
||||
#include "aad.h"
|
||||
|
||||
#ifdef WITH_CJSON
|
||||
#ifdef WITH_AAD
|
||||
#if CJSON_VERSION_MAJOR == 1
|
||||
#if CJSON_VERSION_MINOR <= 7
|
||||
#if CJSON_VERSION_PATCH < 13
|
||||
@ -61,71 +57,14 @@ struct rdp_aad
|
||||
char* kid;
|
||||
char* nonce;
|
||||
char* hostname;
|
||||
char* scope;
|
||||
wLog* log;
|
||||
};
|
||||
|
||||
#ifdef WITH_CJSON
|
||||
#if OPENSSL_VERSION_NUMBER >= 0x30000000L
|
||||
#include <openssl/core_names.h>
|
||||
#else
|
||||
static int BIO_get_line(BIO* bio, char* buf, int size)
|
||||
{
|
||||
int pos = 0;
|
||||
|
||||
if (size <= 1)
|
||||
return 0;
|
||||
|
||||
while (pos < size - 1)
|
||||
{
|
||||
char c = '\0';
|
||||
const int rc = BIO_read(bio, &c, sizeof(c));
|
||||
if (rc < 0)
|
||||
return rc;
|
||||
if (rc > 0)
|
||||
{
|
||||
buf[pos++] = c;
|
||||
if (c == '\n')
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
buf[pos] = '\0';
|
||||
return pos;
|
||||
}
|
||||
#endif
|
||||
|
||||
static char* auth_server = "login.microsoftonline.com";
|
||||
|
||||
static const char nonce_http_request[] = ""
|
||||
"POST /common/oauth2/token HTTP/1.1\r\n"
|
||||
"Host: login.microsoftonline.com\r\n"
|
||||
"Content-Type: application/x-www-form-urlencoded\r\n"
|
||||
"Content-Length: 24\r\n"
|
||||
"\r\n"
|
||||
"grant_type=srv_challenge"
|
||||
"\r\n\r\n";
|
||||
|
||||
static const char token_http_request_header[] =
|
||||
""
|
||||
"POST /common/oauth2/v2.0/token HTTP/1.1\r\n"
|
||||
"Host: login.microsoftonline.com\r\n"
|
||||
"Content-Type: application/x-www-form-urlencoded\r\n"
|
||||
"Content-Length: %lu\r\n"
|
||||
"\r\n";
|
||||
static const char token_http_request_body[] =
|
||||
""
|
||||
"client_id=%s&grant_type=authorization_code"
|
||||
"&code=%s"
|
||||
"&scope=ms-device-service%%3A%%2F%%2Ftermsrv.wvd.microsoft.com%%2Fname%%2F%s%%2Fuser_"
|
||||
"impersonation"
|
||||
"&req_cnf=%s"
|
||||
"&redirect_uri=%s"
|
||||
"\r\n\r\n";
|
||||
#ifdef WITH_AAD
|
||||
|
||||
static BOOL get_encoded_rsa_params(wLog* wlog, rdpPrivateKey* key, char** e, char** n);
|
||||
static BOOL generate_pop_key(rdpAad* aad);
|
||||
static BOOL read_http_message(rdpAad* aad, BIO* bio, long* status_code, char** content,
|
||||
size_t* content_length);
|
||||
|
||||
static SSIZE_T stream_sprintf(wStream* s, const char* fmt, ...)
|
||||
{
|
||||
@ -151,13 +90,6 @@ static SSIZE_T stream_sprintf(wStream* s, const char* fmt, ...)
|
||||
return rc2;
|
||||
}
|
||||
|
||||
static int print_error(const char* str, size_t len, void* u)
|
||||
{
|
||||
wLog* wlog = (wLog*)u;
|
||||
WLog_Print(wlog, WLOG_ERROR, "%s [%" PRIuz "]", str, len);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static BOOL json_get_object(wLog* wlog, cJSON* json, const char* key, cJSON** obj)
|
||||
{
|
||||
WINPR_ASSERT(json);
|
||||
@ -255,88 +187,6 @@ static BOOL json_get_string_alloc(wLog* wlog, cJSON* json, const char* key, char
|
||||
return *result != NULL;
|
||||
}
|
||||
|
||||
static BIO* aad_connect_https(rdpAad* aad, SSL_CTX* ssl_ctx)
|
||||
{
|
||||
WINPR_ASSERT(aad);
|
||||
WINPR_ASSERT(ssl_ctx);
|
||||
|
||||
const int vprc = SSL_CTX_set_default_verify_paths(ssl_ctx);
|
||||
if (vprc != 1)
|
||||
{
|
||||
WLog_Print(aad->log, WLOG_ERROR, "Error setting verify paths");
|
||||
return NULL;
|
||||
}
|
||||
const long mrc = SSL_CTX_set_mode(ssl_ctx, SSL_MODE_AUTO_RETRY);
|
||||
if ((mrc & SSL_MODE_AUTO_RETRY) == 0)
|
||||
WLog_Print(aad->log, WLOG_WARN, "Failed to set SSL_MODE_AUTO_RETRY");
|
||||
|
||||
BIO* bio = BIO_new_ssl_connect(ssl_ctx);
|
||||
if (!bio)
|
||||
{
|
||||
WLog_Print(aad->log, WLOG_ERROR, "Error setting up connection");
|
||||
return NULL;
|
||||
}
|
||||
const long chrc = BIO_set_conn_hostname(bio, auth_server);
|
||||
if (chrc != 1)
|
||||
{
|
||||
WLog_Print(aad->log, WLOG_ERROR, "Error setting BIO hostname");
|
||||
BIO_free(bio);
|
||||
return NULL;
|
||||
}
|
||||
const long cprc = BIO_set_conn_port(bio, "https");
|
||||
if (cprc != 1)
|
||||
{
|
||||
WLog_Print(aad->log, WLOG_ERROR, "Error setting BIO port");
|
||||
BIO_free(bio);
|
||||
return NULL;
|
||||
}
|
||||
return bio;
|
||||
}
|
||||
|
||||
static BOOL aad_logging_bio_write(rdpAad* aad, BIO* bio, const char* str)
|
||||
{
|
||||
WINPR_ASSERT(aad);
|
||||
WINPR_ASSERT(bio);
|
||||
WINPR_ASSERT(str);
|
||||
|
||||
const size_t size = strlen(str);
|
||||
if (size > INT_MAX)
|
||||
return FALSE;
|
||||
ERR_clear_error();
|
||||
if (BIO_write(bio, str, (int)size) < 0)
|
||||
{
|
||||
ERR_print_errors_cb(print_error, aad->log);
|
||||
return FALSE;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static char* aad_read_response(rdpAad* aad, BIO* bio, size_t* plen, const char* what)
|
||||
{
|
||||
WINPR_ASSERT(plen);
|
||||
|
||||
long status_code;
|
||||
char* buffer = NULL;
|
||||
size_t length = 0;
|
||||
|
||||
*plen = 0;
|
||||
if (!read_http_message(aad, bio, &status_code, &buffer, &length))
|
||||
{
|
||||
WLog_Print(aad->log, WLOG_ERROR, "Unable to read %s HTTP response", what);
|
||||
return NULL;
|
||||
}
|
||||
WLog_Print(aad->log, WLOG_DEBUG, "%s HTTP response: %s", what, buffer);
|
||||
|
||||
if (status_code != 200)
|
||||
{
|
||||
WLog_Print(aad->log, WLOG_ERROR, "%s HTTP status code: %li", what, status_code);
|
||||
free(buffer);
|
||||
return NULL;
|
||||
}
|
||||
*plen = length;
|
||||
return buffer;
|
||||
}
|
||||
|
||||
static cJSON* compat_cJSON_ParseWithLength(const char* value, size_t buffer_length)
|
||||
{
|
||||
#if defined(USE_CJSON_COMPAT)
|
||||
@ -349,101 +199,50 @@ static cJSON* compat_cJSON_ParseWithLength(const char* value, size_t buffer_leng
|
||||
return cJSON_ParseWithLength(value, buffer_length);
|
||||
#endif
|
||||
}
|
||||
static BOOL aad_read_and_extract_token_from_json(rdpAad* aad, BIO* bio)
|
||||
{
|
||||
BOOL rc = FALSE;
|
||||
size_t blen = 0;
|
||||
char* buffer = aad_read_response(aad, bio, &blen, "access token");
|
||||
if (!buffer)
|
||||
return FALSE;
|
||||
|
||||
cJSON* json = compat_cJSON_ParseWithLength(buffer, blen);
|
||||
if (!json)
|
||||
static BOOL aad_get_nonce(rdpAad* aad)
|
||||
{
|
||||
BOOL ret = FALSE;
|
||||
BYTE* response = NULL;
|
||||
long resp_code = 0;
|
||||
size_t response_length = 0;
|
||||
cJSON* json = NULL;
|
||||
|
||||
if (!freerdp_http_request("https://login.microsoftonline.com/common/oauth2/v2.0/token",
|
||||
"grant_type=srv_challenge", &resp_code, &response, &response_length))
|
||||
{
|
||||
WLog_Print(aad->log, WLOG_ERROR, "Failed to parse JSON response");
|
||||
WLog_Print(aad->log, WLOG_ERROR, "nonce request failed");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (!json_get_string_alloc(aad->log, json, "access_token", &aad->access_token))
|
||||
if (resp_code != 200)
|
||||
{
|
||||
WLog_Print(aad->log, WLOG_ERROR,
|
||||
"Could not find \"access_token\" property in JSON response");
|
||||
"Server unwilling to provide nonce; returned status code %li", resp_code);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
rc = TRUE;
|
||||
fail:
|
||||
free(buffer);
|
||||
cJSON_Delete(json);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static BOOL aad_read_and_extrace_nonce_from_json(rdpAad* aad, BIO* bio)
|
||||
{
|
||||
BOOL rc = FALSE;
|
||||
size_t blen = 0;
|
||||
char* buffer = aad_read_response(aad, bio, &blen, "Nonce");
|
||||
if (!buffer)
|
||||
return FALSE;
|
||||
|
||||
/* Extract the nonce from the response */
|
||||
cJSON* json = compat_cJSON_ParseWithLength(buffer, blen);
|
||||
json = cJSON_ParseWithLength((const char*)response, response_length);
|
||||
if (!json)
|
||||
{
|
||||
WLog_Print(aad->log, WLOG_ERROR, "Failed to parse JSON response");
|
||||
WLog_Print(aad->log, WLOG_ERROR, "Failed to parse nonce response");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (!json_get_string_alloc(aad->log, json, "Nonce", &aad->nonce))
|
||||
{
|
||||
WLog_Print(aad->log, WLOG_ERROR, "Could not find \"Nonce\" property in JSON response");
|
||||
goto fail;
|
||||
}
|
||||
rc = TRUE;
|
||||
|
||||
ret = TRUE;
|
||||
|
||||
fail:
|
||||
free(buffer);
|
||||
free(response);
|
||||
cJSON_Delete(json);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static BOOL aad_send_token_request(rdpAad* aad, BIO* bio, const char* auth_code,
|
||||
const char* client_id, const char* redirect_uri)
|
||||
{
|
||||
BOOL rc = FALSE;
|
||||
|
||||
char* req_body = NULL;
|
||||
char* req_header = NULL;
|
||||
size_t req_body_len = 0;
|
||||
size_t req_header_len = 0;
|
||||
const int trc = winpr_asprintf(&req_body, &req_body_len, token_http_request_body, client_id,
|
||||
auth_code, aad->hostname, aad->kid, redirect_uri);
|
||||
if (trc < 0)
|
||||
goto fail;
|
||||
const int trh = winpr_asprintf(&req_header, &req_header_len, token_http_request_header, trc);
|
||||
if (trh < 0)
|
||||
goto fail;
|
||||
|
||||
WLog_Print(aad->log, WLOG_DEBUG, "HTTP access token request: %s%s", req_header, req_body);
|
||||
|
||||
if (!aad_logging_bio_write(aad, bio, req_header))
|
||||
goto fail;
|
||||
if (!aad_logging_bio_write(aad, bio, req_body))
|
||||
goto fail;
|
||||
rc = TRUE;
|
||||
fail:
|
||||
free(req_body);
|
||||
free(req_header);
|
||||
return rc;
|
||||
return ret;
|
||||
}
|
||||
|
||||
int aad_client_begin(rdpAad* aad)
|
||||
{
|
||||
int ret = -1;
|
||||
SSL_CTX* ssl_ctx = NULL;
|
||||
BIO* bio = NULL;
|
||||
char* auth_code = NULL;
|
||||
const char* client_id = NULL;
|
||||
const char* redirect_uri = NULL;
|
||||
size_t size = 0;
|
||||
|
||||
WINPR_ASSERT(aad);
|
||||
WINPR_ASSERT(aad->rdpcontext);
|
||||
@ -473,60 +272,37 @@ int aad_client_begin(rdpAad* aad)
|
||||
if (p)
|
||||
*p = '\0';
|
||||
|
||||
if (winpr_asprintf(&aad->scope, &size,
|
||||
"ms-device-service%%3A%%2F%%2Ftermsrv.wvd.microsoft.com%%2Fname%%2F%s%%"
|
||||
"2Fuser_impersonation",
|
||||
aad->hostname) <= 0)
|
||||
return -1;
|
||||
|
||||
if (!generate_pop_key(aad))
|
||||
goto fail;
|
||||
return -1;
|
||||
|
||||
/* Obtain an oauth authorization code */
|
||||
if (!instance->GetAadAuthCode)
|
||||
if (!instance->GetRDSAADAccessToken)
|
||||
{
|
||||
WLog_Print(aad->log, WLOG_ERROR, "instance->GetAadAuthCode == NULL");
|
||||
goto fail;
|
||||
WLog_Print(aad->log, WLOG_ERROR, "instance->GetRDSAADAccessToken == NULL");
|
||||
return -1;
|
||||
}
|
||||
const BOOL arc =
|
||||
instance->GetAadAuthCode(instance, aad->hostname, &auth_code, &client_id, &redirect_uri);
|
||||
instance->GetRDSAADAccessToken(instance, aad->scope, aad->kid, &aad->access_token);
|
||||
if (!arc)
|
||||
{
|
||||
WLog_Print(aad->log, WLOG_ERROR, "Unable to obtain authorization code");
|
||||
goto fail;
|
||||
WLog_Print(aad->log, WLOG_ERROR, "Unable to obtain access token");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Set up an ssl connection to the authorization server */
|
||||
ssl_ctx = SSL_CTX_new(TLS_client_method());
|
||||
if (!ssl_ctx)
|
||||
{
|
||||
WLog_Print(aad->log, WLOG_ERROR, "Error setting up SSL context");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
bio = aad_connect_https(aad, ssl_ctx);
|
||||
if (!bio)
|
||||
goto fail;
|
||||
|
||||
/* Construct and send the token request message */
|
||||
if (!aad_send_token_request(aad, bio, auth_code, client_id, redirect_uri))
|
||||
goto fail;
|
||||
|
||||
/* Extract the access token from the JSON response */
|
||||
if (!aad_read_and_extract_token_from_json(aad, bio))
|
||||
goto fail;
|
||||
|
||||
/* Send the nonce request message */
|
||||
WLog_Print(aad->log, WLOG_DEBUG, "HTTP nonce request: %s", nonce_http_request);
|
||||
if (!aad_get_nonce(aad))
|
||||
{
|
||||
WLog_Print(aad->log, WLOG_ERROR, "Unable to obtain nonce");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!aad_logging_bio_write(aad, bio, nonce_http_request))
|
||||
goto fail;
|
||||
|
||||
/* Read in the response */
|
||||
if (!aad_read_and_extrace_nonce_from_json(aad, bio))
|
||||
goto fail;
|
||||
|
||||
ret = 1;
|
||||
|
||||
fail:
|
||||
BIO_free_all(bio);
|
||||
SSL_CTX_free(ssl_ctx);
|
||||
free(auth_code);
|
||||
return ret;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static char* aad_create_jws_header(rdpAad* aad)
|
||||
@ -792,85 +568,6 @@ int aad_recv(rdpAad* aad, wStream* s)
|
||||
}
|
||||
}
|
||||
|
||||
static BOOL read_http_message(rdpAad* aad, BIO* bio, long* status_code, char** content,
|
||||
size_t* content_length)
|
||||
{
|
||||
char buffer[1024] = { 0 };
|
||||
|
||||
WINPR_ASSERT(aad);
|
||||
WINPR_ASSERT(status_code);
|
||||
WINPR_ASSERT(content);
|
||||
WINPR_ASSERT(content_length);
|
||||
|
||||
*content_length = 0;
|
||||
const int rb = BIO_get_line(bio, buffer, sizeof(buffer));
|
||||
if (rb <= 0)
|
||||
{
|
||||
WLog_Print(aad->log, WLOG_ERROR, "Error reading HTTP response [BIO_get_line %d]", rb);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (sscanf(buffer, "HTTP/%*u.%*u %li %*[^\r\n]\r\n", status_code) < 1)
|
||||
{
|
||||
WLog_Print(aad->log, WLOG_ERROR, "Invalid HTTP response status line");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
do
|
||||
{
|
||||
const int rbb = BIO_get_line(bio, buffer, sizeof(buffer));
|
||||
if (rbb <= 0)
|
||||
{
|
||||
WLog_Print(aad->log, WLOG_ERROR, "Error reading HTTP response [BIO_get_line %d]", rbb);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
char* val = NULL;
|
||||
char* name = strtok_s(buffer, ":", &val);
|
||||
if (name && (_stricmp(name, "content-length") == 0))
|
||||
{
|
||||
errno = 0;
|
||||
*content_length = strtoul(val, NULL, 10);
|
||||
switch (errno)
|
||||
{
|
||||
case 0:
|
||||
break;
|
||||
default:
|
||||
WLog_Print(aad->log, WLOG_ERROR, "strtoul(%s) returned %s [%d]", val,
|
||||
strerror(errno), errno);
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
} while (strcmp(buffer, "\r\n") != 0);
|
||||
|
||||
if (*content_length == 0)
|
||||
return TRUE;
|
||||
|
||||
*content = calloc(*content_length + 1, sizeof(char));
|
||||
if (!*content)
|
||||
return FALSE;
|
||||
|
||||
size_t offset = 0;
|
||||
while (offset < *content_length)
|
||||
{
|
||||
const size_t diff = *content_length - offset;
|
||||
int len = (int)diff;
|
||||
if (diff > INT_MAX)
|
||||
len = INT_MAX;
|
||||
const int brc = BIO_read(bio, &(*content)[offset], len);
|
||||
if (brc <= 0)
|
||||
{
|
||||
free(*content);
|
||||
WLog_Print(aad->log, WLOG_ERROR,
|
||||
"Error reading HTTP response body (BIO_read returned %d)", brc);
|
||||
return FALSE;
|
||||
}
|
||||
offset += (size_t)brc;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static BOOL generate_rsa_2048(rdpAad* aad)
|
||||
{
|
||||
WINPR_ASSERT(aad);
|
||||
@ -1080,6 +777,7 @@ void aad_free(rdpAad* aad)
|
||||
return;
|
||||
|
||||
free(aad->hostname);
|
||||
free(aad->scope);
|
||||
free(aad->nonce);
|
||||
free(aad->access_token);
|
||||
free(aad->kid);
|
||||
@ -1096,7 +794,7 @@ AAD_STATE aad_get_state(rdpAad* aad)
|
||||
|
||||
BOOL aad_is_supported(void)
|
||||
{
|
||||
#ifdef WITH_CJSON
|
||||
#ifdef WITH_AAD
|
||||
return TRUE;
|
||||
#else
|
||||
return FALSE;
|
||||
|
@ -44,7 +44,7 @@
|
||||
#include "../utils.h"
|
||||
#include "../settings.h"
|
||||
|
||||
#ifdef WITH_CJSON
|
||||
#ifdef WITH_AAD
|
||||
#include <cjson/cJSON.h>
|
||||
#endif
|
||||
|
||||
@ -59,7 +59,7 @@ typedef struct rdp_arm rdpArm;
|
||||
|
||||
#define TAG FREERDP_TAG("core.gateway.arm")
|
||||
|
||||
#ifdef WITH_CJSON
|
||||
#ifdef WITH_AAD
|
||||
static BOOL arm_tls_connect(rdpArm* arm, rdpTls* tls, int timeout)
|
||||
{
|
||||
WINPR_ASSERT(arm);
|
||||
@ -152,8 +152,14 @@ static wStream* arm_build_http_request(rdpArm* arm, const char* method,
|
||||
HttpRequest* request = NULL;
|
||||
const char* uri;
|
||||
|
||||
if (!arm || !method || !content_type)
|
||||
return NULL;
|
||||
WINPR_ASSERT(arm);
|
||||
WINPR_ASSERT(method);
|
||||
WINPR_ASSERT(content_type);
|
||||
|
||||
WINPR_ASSERT(arm->context);
|
||||
|
||||
freerdp* instance = arm->context->instance;
|
||||
WINPR_ASSERT(instance);
|
||||
|
||||
uri = http_context_get_uri(arm->http);
|
||||
request = http_request_new();
|
||||
@ -164,15 +170,37 @@ static wStream* arm_build_http_request(rdpArm* arm, const char* method,
|
||||
if (!http_request_set_method(request, method) || !http_request_set_uri(request, uri))
|
||||
goto out;
|
||||
|
||||
else if (freerdp_settings_get_string(arm->context->settings, FreeRDP_GatewayHttpExtAuthBearer))
|
||||
if (!freerdp_settings_get_string(arm->context->settings, FreeRDP_GatewayHttpExtAuthBearer))
|
||||
{
|
||||
if (!http_request_set_auth_scheme(request, "Bearer") ||
|
||||
!http_request_set_auth_param(
|
||||
request, freerdp_settings_get_string(arm->context->settings,
|
||||
FreeRDP_GatewayHttpExtAuthBearer)))
|
||||
char* token = NULL;
|
||||
|
||||
if (!instance->GetAVDAccessToken)
|
||||
{
|
||||
WLog_ERR(TAG, "No authorization token provided");
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (!instance->GetAVDAccessToken(instance, &token))
|
||||
{
|
||||
WLog_ERR(TAG, "Unable to obtain access token");
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (!freerdp_settings_set_string(arm->context->settings, FreeRDP_GatewayHttpExtAuthBearer,
|
||||
token))
|
||||
{
|
||||
free(token);
|
||||
goto out;
|
||||
}
|
||||
free(token);
|
||||
}
|
||||
|
||||
if (!http_request_set_auth_scheme(request, "Bearer") ||
|
||||
!http_request_set_auth_param(
|
||||
request,
|
||||
freerdp_settings_get_string(arm->context->settings, FreeRDP_GatewayHttpExtAuthBearer)))
|
||||
goto out;
|
||||
|
||||
if (!http_request_set_transfer_encoding(request, transferEncoding) ||
|
||||
!http_request_set_content_length(request, content_length) ||
|
||||
!http_request_set_content_type(request, content_type))
|
||||
@ -304,6 +332,17 @@ static BOOL arm_fill_gateway_parameters(rdpArm* arm, const char* message)
|
||||
else
|
||||
status = TRUE;
|
||||
}
|
||||
|
||||
const cJSON* hostname = cJSON_GetObjectItem(json, "redirectedServerName");
|
||||
if (cJSON_GetStringValue(hostname))
|
||||
{
|
||||
if (!freerdp_settings_set_string(arm->context->settings, FreeRDP_ServerHostname,
|
||||
cJSON_GetStringValue(hostname)))
|
||||
status = FALSE;
|
||||
else
|
||||
status = TRUE;
|
||||
}
|
||||
|
||||
cJSON_Delete(json);
|
||||
}
|
||||
return status;
|
||||
@ -313,7 +352,7 @@ static BOOL arm_fill_gateway_parameters(rdpArm* arm, const char* message)
|
||||
|
||||
BOOL arm_resolve_endpoint(rdpContext* context, DWORD timeout)
|
||||
{
|
||||
#ifndef WITH_CJSON
|
||||
#ifndef WITH_AAD
|
||||
WLog_ERR(TAG, "arm gateway support not compiled in");
|
||||
return FALSE;
|
||||
#else
|
||||
|
@ -33,6 +33,7 @@ set(${MODULE_PREFIX}_SRCS
|
||||
smartcard_pack.c
|
||||
smartcard_call.c
|
||||
stopwatch.c
|
||||
http.c
|
||||
)
|
||||
|
||||
freerdp_module_add(${${MODULE_PREFIX}_SRCS})
|
||||
|
231
libfreerdp/utils/http.c
Normal file
231
libfreerdp/utils/http.c
Normal file
@ -0,0 +1,231 @@
|
||||
/**
|
||||
* FreeRDP: A Remote Desktop Protocol Implementation
|
||||
* Simple HTTP client request utility
|
||||
*
|
||||
* Copyright 2023 Isaac Klein <fifthdegree@protonmail.com>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <freerdp/config.h>
|
||||
#include <freerdp/utils/http.h>
|
||||
|
||||
#include <winpr/assert.h>
|
||||
#include <winpr/string.h>
|
||||
|
||||
#include <openssl/bio.h>
|
||||
#include <openssl/ssl.h>
|
||||
#include <openssl/err.h>
|
||||
|
||||
#include <freerdp/log.h>
|
||||
#define TAG FREERDP_TAG("utils.http")
|
||||
|
||||
static const char get_header_fmt[] = "GET %s HTTP/1.1\r\n"
|
||||
"Host: %s\r\n"
|
||||
"\r\n";
|
||||
|
||||
static const char post_header_fmt[] = "POST %s HTTP/1.1\r\n"
|
||||
"Host: %s\r\n"
|
||||
"Content-Type: application/x-www-form-urlencoded\r\n"
|
||||
"Content-Length: %lu\r\n"
|
||||
"\r\n";
|
||||
|
||||
static void log_errors(const char* msg)
|
||||
{
|
||||
unsigned long ec = 0;
|
||||
while ((ec = ERR_get_error()))
|
||||
WLog_ERR(TAG, "%s: %s", msg, ERR_error_string(ec, NULL));
|
||||
}
|
||||
|
||||
BOOL freerdp_http_request(const char* url, const char* body, long* status_code, BYTE** response,
|
||||
size_t* response_length)
|
||||
{
|
||||
BOOL ret = FALSE;
|
||||
char* hostname = NULL;
|
||||
const char* path = NULL;
|
||||
char* headers = NULL;
|
||||
size_t size = 0;
|
||||
int status = 0;
|
||||
char buffer[1024] = { 0 };
|
||||
BIO* bio = NULL;
|
||||
SSL_CTX* ssl_ctx = NULL;
|
||||
|
||||
WINPR_ASSERT(status_code);
|
||||
WINPR_ASSERT(response);
|
||||
WINPR_ASSERT(response_length);
|
||||
|
||||
*response = NULL;
|
||||
|
||||
if (!url || strlen(url) < 8 || strncmp(url, "https://", 8) != 0 ||
|
||||
!(path = strchr(url + 8, '/')))
|
||||
{
|
||||
WLog_ERR(TAG, "invalid url provided");
|
||||
goto out;
|
||||
}
|
||||
|
||||
hostname = calloc(1, path - (url + 8) + 1);
|
||||
if (!hostname)
|
||||
return FALSE;
|
||||
strncpy(hostname, url + 8, path - (url + 8));
|
||||
|
||||
if (body)
|
||||
{
|
||||
if (winpr_asprintf(&headers, &size, post_header_fmt, path, hostname, strlen(body)) < 0)
|
||||
return FALSE;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (winpr_asprintf(&headers, &size, get_header_fmt, path, hostname) < 0)
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
ssl_ctx = SSL_CTX_new(TLS_client_method());
|
||||
|
||||
if (!ssl_ctx)
|
||||
{
|
||||
log_errors("could not set up ssl context");
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (!SSL_CTX_set_default_verify_paths(ssl_ctx))
|
||||
{
|
||||
log_errors("could not set ssl context verify paths");
|
||||
goto out;
|
||||
}
|
||||
|
||||
SSL_CTX_set_mode(ssl_ctx, SSL_MODE_AUTO_RETRY);
|
||||
|
||||
bio = BIO_new_ssl_connect(ssl_ctx);
|
||||
if (!bio)
|
||||
{
|
||||
log_errors("could not set up connection");
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (BIO_set_conn_port(bio, "https") <= 0)
|
||||
{
|
||||
log_errors("could not set port");
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (!BIO_set_conn_hostname(bio, hostname))
|
||||
{
|
||||
log_errors("could not set hostname");
|
||||
goto out;
|
||||
}
|
||||
|
||||
WLog_DBG(TAG, "headers:\n%s", headers);
|
||||
ERR_clear_error();
|
||||
if (BIO_write(bio, headers, strlen(headers)) < 0)
|
||||
{
|
||||
log_errors("could not write headers");
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (body)
|
||||
{
|
||||
WLog_DBG(TAG, "body:\n%s", body);
|
||||
|
||||
if (strlen(body) > INT_MAX)
|
||||
{
|
||||
WLog_ERR(TAG, "body too long!");
|
||||
goto out;
|
||||
}
|
||||
|
||||
ERR_clear_error();
|
||||
if (BIO_write(bio, body, strlen(body)) < 0)
|
||||
{
|
||||
log_errors("could not write body");
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
status = BIO_get_line(bio, buffer, sizeof(buffer));
|
||||
if (status <= 0)
|
||||
{
|
||||
log_errors("could not read response");
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (sscanf(buffer, "HTTP/1.1 %li %*[^\r\n]\r\n", status_code) < 1)
|
||||
{
|
||||
WLog_ERR(TAG, "invalid HTTP status line");
|
||||
goto out;
|
||||
}
|
||||
|
||||
do
|
||||
{
|
||||
status = BIO_get_line(bio, buffer, sizeof(buffer));
|
||||
if (status <= 0)
|
||||
{
|
||||
log_errors("could not read response");
|
||||
goto out;
|
||||
}
|
||||
|
||||
char* val = NULL;
|
||||
char* name = strtok_s(buffer, ":", &val);
|
||||
if (name && (_stricmp(name, "content-length") == 0))
|
||||
{
|
||||
errno = 0;
|
||||
*response_length = strtoul(val, NULL, 10);
|
||||
if (errno)
|
||||
{
|
||||
WLog_ERR(TAG, "could not parse content length (%s): %s [%d]", val, strerror(errno),
|
||||
errno);
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
} while (strcmp(buffer, "\r\n") != 0);
|
||||
|
||||
if (*response_length > 0)
|
||||
{
|
||||
*response = calloc(1, *response_length + 1);
|
||||
if (!*response)
|
||||
goto out;
|
||||
|
||||
if (*response_length > INT_MAX)
|
||||
{
|
||||
WLog_ERR(TAG, "response too long!");
|
||||
goto out;
|
||||
}
|
||||
|
||||
BYTE* p = *response;
|
||||
int left = *response_length;
|
||||
while (left > 0)
|
||||
{
|
||||
status = BIO_read(bio, p, left);
|
||||
if (status <= 0)
|
||||
{
|
||||
log_errors("could not read response");
|
||||
goto out;
|
||||
}
|
||||
p += status;
|
||||
left -= status;
|
||||
}
|
||||
}
|
||||
|
||||
ret = TRUE;
|
||||
|
||||
out:
|
||||
if (!ret)
|
||||
{
|
||||
free(*response);
|
||||
*response = NULL;
|
||||
*response_length = 0;
|
||||
}
|
||||
free(hostname);
|
||||
free(headers);
|
||||
BIO_free_all(bio);
|
||||
SSL_CTX_free(ssl_ctx);
|
||||
return ret;
|
||||
}
|
@ -12,7 +12,7 @@
|
||||
WITH_OPENH264=0
|
||||
WITH_OPENSSL=1
|
||||
WITH_FFMPEG=1
|
||||
WITH_CJSON=1
|
||||
WITH_AAD=1
|
||||
BUILD_DEPS=1
|
||||
DEPS_ONLY=0
|
||||
NDK_TARGET=21
|
||||
|
@ -12,7 +12,7 @@
|
||||
WITH_OPENH264=1
|
||||
WITH_OPENSSL=1
|
||||
WITH_FFMPEG=1
|
||||
WITH_CJSON=1
|
||||
WITH_AAD=1
|
||||
BUILD_DEPS=1
|
||||
DEPS_ONLY=0
|
||||
NDK_TARGET=21
|
||||
|
@ -12,7 +12,7 @@ CJSON_HASH=d348621ca93571343a56862df7de4ff3bc9b5667
|
||||
WITH_OPENH264=0
|
||||
WITH_OPENSSL=0
|
||||
WITH_FFMPEG=0
|
||||
WITH_CJSON=0
|
||||
WITH_AAD=0
|
||||
|
||||
SRC_DIR=$(dirname "${BASH_SOURCE[0]}")
|
||||
SRC_DIR=$(realpath "$SRC_DIR")
|
||||
@ -50,7 +50,7 @@ do
|
||||
shift
|
||||
;;
|
||||
--cjson)
|
||||
WITH_CJSON=1
|
||||
WITH_AAD=1
|
||||
shift
|
||||
;;
|
||||
--openssl)
|
||||
@ -162,7 +162,7 @@ do
|
||||
else
|
||||
CMAKE_CMD_ARGS="$CMAKE_CMD_ARGS -DWITH_FFMPEG=OFF"
|
||||
fi
|
||||
if [ $WITH_CJSON -ne 0 ];
|
||||
if [ $WITH_AAD -ne 0 ];
|
||||
then
|
||||
if [ $BUILD_DEPS -ne 0 ];
|
||||
then
|
||||
|
@ -12,7 +12,7 @@
|
||||
WITH_OPENH264=0
|
||||
WITH_OPENSSL=1
|
||||
WITH_FFMPEG=1
|
||||
WITH_CJSON=1
|
||||
WITH_AAD=1
|
||||
BUILD_DEPS=1
|
||||
DEPS_ONLY=0
|
||||
NDK_TARGET=23
|
||||
|
@ -12,7 +12,7 @@
|
||||
WITH_OPENH264=0
|
||||
WITH_OPENSSL=1
|
||||
WITH_FFMPEG=0
|
||||
WITH_CJSON=1
|
||||
WITH_AAD=1
|
||||
BUILD_DEPS=1
|
||||
DEPS_ONLY=0
|
||||
NDK_TARGET=21
|
||||
|
Loading…
Reference in New Issue
Block a user