Milestone 5: deliver embedded RDP sessions and lifecycle hardening
This commit is contained in:
420
third_party/FreeRDP/libfreerdp/utils/http.c
vendored
Normal file
420
third_party/FreeRDP/libfreerdp/utils/http.c
vendored
Normal file
@@ -0,0 +1,420 @@
|
||||
/**
|
||||
* 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";
|
||||
|
||||
#define log_errors(log, msg) log_errors_(log, msg, __FILE__, __func__, __LINE__)
|
||||
static void log_errors_(wLog* log, const char* msg, const char* file, const char* fkt, size_t line)
|
||||
{
|
||||
const DWORD level = WLOG_ERROR;
|
||||
unsigned long ec = 0;
|
||||
|
||||
if (!WLog_IsLevelActive(log, level))
|
||||
return;
|
||||
|
||||
BOOL error_logged = FALSE;
|
||||
while ((ec = ERR_get_error()))
|
||||
{
|
||||
error_logged = TRUE;
|
||||
WLog_PrintTextMessage(log, level, line, file, fkt, "%s: %s", msg,
|
||||
ERR_error_string(ec, nullptr));
|
||||
}
|
||||
if (!error_logged)
|
||||
WLog_PrintTextMessage(log, level, line, file, fkt, "%s (no details available)", msg);
|
||||
}
|
||||
|
||||
static int get_line(BIO* bio, char* buffer, size_t size)
|
||||
{
|
||||
#if !defined(OPENSSL_VERSION_MAJOR) || (OPENSSL_VERSION_MAJOR < 3)
|
||||
if (size <= 1)
|
||||
return -1;
|
||||
|
||||
size_t pos = 0;
|
||||
do
|
||||
{
|
||||
int rc = BIO_read(bio, &buffer[pos], 1);
|
||||
if (rc <= 0)
|
||||
return rc;
|
||||
char cur = buffer[pos];
|
||||
pos += rc;
|
||||
if ((cur == '\n') || (pos >= size - 1))
|
||||
{
|
||||
buffer[pos] = '\0';
|
||||
return (int)pos;
|
||||
}
|
||||
} while (1);
|
||||
#else
|
||||
if (size > INT32_MAX)
|
||||
return -1;
|
||||
return BIO_get_line(bio, buffer, (int)size);
|
||||
#endif
|
||||
}
|
||||
|
||||
BOOL freerdp_http_request(const char* url, const char* body, long* status_code, BYTE** response,
|
||||
size_t* response_length)
|
||||
{
|
||||
BOOL ret = FALSE;
|
||||
char* hostname = nullptr;
|
||||
const char* path = nullptr;
|
||||
char* headers = nullptr;
|
||||
size_t size = 0;
|
||||
int status = 0;
|
||||
char buffer[1024] = WINPR_C_ARRAY_INIT;
|
||||
BIO* bio = nullptr;
|
||||
SSL_CTX* ssl_ctx = nullptr;
|
||||
SSL* ssl = nullptr;
|
||||
|
||||
WINPR_ASSERT(status_code);
|
||||
WINPR_ASSERT(response);
|
||||
WINPR_ASSERT(response_length);
|
||||
|
||||
wLog* log = WLog_Get(TAG);
|
||||
WINPR_ASSERT(log);
|
||||
|
||||
*response = nullptr;
|
||||
|
||||
if (!url || strnlen(url, 8) < 8 || strncmp(url, "https://", 8) != 0 ||
|
||||
!(path = strchr(url + 8, '/')))
|
||||
{
|
||||
WLog_Print(log, WLOG_ERROR, "invalid url provided");
|
||||
goto out;
|
||||
}
|
||||
|
||||
{
|
||||
const size_t len = WINPR_ASSERTING_INT_CAST(size_t, path - (url + 8));
|
||||
hostname = strndup(&url[8], len);
|
||||
}
|
||||
if (!hostname)
|
||||
return FALSE;
|
||||
|
||||
{
|
||||
size_t blen = 0;
|
||||
if (body)
|
||||
{
|
||||
blen = strlen(body);
|
||||
if (winpr_asprintf(&headers, &size, post_header_fmt, path, hostname, blen) < 0)
|
||||
{
|
||||
free(hostname);
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (winpr_asprintf(&headers, &size, get_header_fmt, path, hostname) < 0)
|
||||
{
|
||||
free(hostname);
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
ssl_ctx = SSL_CTX_new(TLS_client_method());
|
||||
|
||||
if (!ssl_ctx)
|
||||
{
|
||||
log_errors(log, "could not set up ssl context");
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (!SSL_CTX_set_default_verify_paths(ssl_ctx))
|
||||
{
|
||||
log_errors(log, "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(log, "could not set up connection");
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (BIO_set_conn_port(bio, "https") <= 0)
|
||||
{
|
||||
log_errors(log, "could not set port");
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (!BIO_set_conn_hostname(bio, hostname))
|
||||
{
|
||||
log_errors(log, "could not set hostname");
|
||||
goto out;
|
||||
}
|
||||
|
||||
BIO_get_ssl(bio, &ssl);
|
||||
if (!ssl)
|
||||
{
|
||||
log_errors(log, "could not get ssl");
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (!SSL_set_tlsext_host_name(ssl, hostname))
|
||||
{
|
||||
log_errors(log, "could not set sni hostname");
|
||||
goto out;
|
||||
}
|
||||
|
||||
WLog_Print(log, WLOG_DEBUG, "headers:\n%s", headers);
|
||||
ERR_clear_error();
|
||||
|
||||
{
|
||||
const size_t hlen = strnlen(headers, size);
|
||||
if (hlen > INT32_MAX)
|
||||
goto out;
|
||||
|
||||
if (BIO_write(bio, headers, (int)hlen) < 0)
|
||||
{
|
||||
log_errors(log, "could not write headers");
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (body)
|
||||
{
|
||||
WLog_Print(log, WLOG_DEBUG, "body:\n%s", body);
|
||||
|
||||
if (blen > INT_MAX)
|
||||
{
|
||||
WLog_Print(log, WLOG_ERROR, "body too long!");
|
||||
goto out;
|
||||
}
|
||||
|
||||
ERR_clear_error();
|
||||
if (BIO_write(bio, body, (int)blen) < 0)
|
||||
{
|
||||
log_errors(log, "could not write body");
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
status = get_line(bio, buffer, sizeof(buffer));
|
||||
if (status <= 0)
|
||||
{
|
||||
log_errors(log, "could not read response");
|
||||
goto out;
|
||||
}
|
||||
|
||||
const char header[9] = { 'H', 'T', 'T', 'P', '/', '1', '.', '1', ' ' };
|
||||
if ((status < (INT64)sizeof(header)) || (strncmp(header, buffer, sizeof(header)) != 0))
|
||||
{
|
||||
WLog_Print(log, WLOG_ERROR, "invalid HTTP status header");
|
||||
goto out;
|
||||
}
|
||||
|
||||
errno = 0;
|
||||
*status_code = strtol(&buffer[sizeof(header)], nullptr, 0);
|
||||
if (errno != 0)
|
||||
{
|
||||
char ebuffer[256] = WINPR_C_ARRAY_INIT;
|
||||
WLog_Print(log, WLOG_ERROR, "invalid HTTP status line: %s [%d]",
|
||||
winpr_strerror(errno, ebuffer, sizeof(ebuffer)), errno);
|
||||
goto out;
|
||||
}
|
||||
|
||||
do
|
||||
{
|
||||
status = get_line(bio, buffer, sizeof(buffer));
|
||||
if (status <= 0)
|
||||
{
|
||||
log_errors(log, "could not read response");
|
||||
goto out;
|
||||
}
|
||||
|
||||
char* val = nullptr;
|
||||
char* name = strtok_s(buffer, ":", &val);
|
||||
if (name && (_stricmp(name, "content-length") == 0))
|
||||
{
|
||||
errno = 0;
|
||||
*response_length = strtoul(val, nullptr, 10);
|
||||
if (errno)
|
||||
{
|
||||
char ebuffer[256] = WINPR_C_ARRAY_INIT;
|
||||
WLog_Print(log, WLOG_ERROR, "could not parse content length (%s): %s [%d]", val,
|
||||
winpr_strerror(errno, ebuffer, sizeof(ebuffer)), errno);
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
} while (strcmp(buffer, "\r\n") != 0);
|
||||
|
||||
if (*response_length > 0)
|
||||
{
|
||||
if (*response_length > INT_MAX)
|
||||
{
|
||||
WLog_Print(log, WLOG_ERROR, "response too long!");
|
||||
goto out;
|
||||
}
|
||||
|
||||
*response = calloc(1, *response_length + 1);
|
||||
if (!*response)
|
||||
goto out;
|
||||
|
||||
BYTE* p = *response;
|
||||
size_t left = *response_length;
|
||||
while (left > 0)
|
||||
{
|
||||
const int rd = (left < INT32_MAX) ? (int)left : INT32_MAX;
|
||||
status = BIO_read(bio, p, rd);
|
||||
if (status <= 0)
|
||||
{
|
||||
log_errors(log, "could not read response");
|
||||
goto out;
|
||||
}
|
||||
p += status;
|
||||
if ((size_t)status > left)
|
||||
break;
|
||||
left -= (size_t)status;
|
||||
}
|
||||
}
|
||||
|
||||
WLog_Print(log, WLOG_DEBUG, "response[%" PRIuz "]:\n%s", *response_length,
|
||||
(const char*)(*response));
|
||||
ret = TRUE;
|
||||
|
||||
out:
|
||||
if (!ret)
|
||||
{
|
||||
free(*response);
|
||||
*response = nullptr;
|
||||
*response_length = 0;
|
||||
}
|
||||
free(hostname);
|
||||
free(headers);
|
||||
BIO_free_all(bio);
|
||||
SSL_CTX_free(ssl_ctx);
|
||||
return ret;
|
||||
}
|
||||
|
||||
const char* freerdp_http_status_string(long status)
|
||||
{
|
||||
switch (status)
|
||||
{
|
||||
case HTTP_STATUS_CONTINUE:
|
||||
return "HTTP_STATUS_CONTINUE";
|
||||
case HTTP_STATUS_SWITCH_PROTOCOLS:
|
||||
return "HTTP_STATUS_SWITCH_PROTOCOLS";
|
||||
case HTTP_STATUS_OK:
|
||||
return "HTTP_STATUS_OK";
|
||||
case HTTP_STATUS_CREATED:
|
||||
return "HTTP_STATUS_CREATED";
|
||||
case HTTP_STATUS_ACCEPTED:
|
||||
return "HTTP_STATUS_ACCEPTED";
|
||||
case HTTP_STATUS_PARTIAL:
|
||||
return "HTTP_STATUS_PARTIAL";
|
||||
case HTTP_STATUS_NO_CONTENT:
|
||||
return "HTTP_STATUS_NO_CONTENT";
|
||||
case HTTP_STATUS_RESET_CONTENT:
|
||||
return "HTTP_STATUS_RESET_CONTENT";
|
||||
case HTTP_STATUS_PARTIAL_CONTENT:
|
||||
return "HTTP_STATUS_PARTIAL_CONTENT";
|
||||
case HTTP_STATUS_WEBDAV_MULTI_STATUS:
|
||||
return "HTTP_STATUS_WEBDAV_MULTI_STATUS";
|
||||
case HTTP_STATUS_AMBIGUOUS:
|
||||
return "HTTP_STATUS_AMBIGUOUS";
|
||||
case HTTP_STATUS_MOVED:
|
||||
return "HTTP_STATUS_MOVED";
|
||||
case HTTP_STATUS_REDIRECT:
|
||||
return "HTTP_STATUS_REDIRECT";
|
||||
case HTTP_STATUS_REDIRECT_METHOD:
|
||||
return "HTTP_STATUS_REDIRECT_METHOD";
|
||||
case HTTP_STATUS_NOT_MODIFIED:
|
||||
return "HTTP_STATUS_NOT_MODIFIED";
|
||||
case HTTP_STATUS_USE_PROXY:
|
||||
return "HTTP_STATUS_USE_PROXY";
|
||||
case HTTP_STATUS_REDIRECT_KEEP_VERB:
|
||||
return "HTTP_STATUS_REDIRECT_KEEP_VERB";
|
||||
case HTTP_STATUS_BAD_REQUEST:
|
||||
return "HTTP_STATUS_BAD_REQUEST";
|
||||
case HTTP_STATUS_DENIED:
|
||||
return "HTTP_STATUS_DENIED";
|
||||
case HTTP_STATUS_PAYMENT_REQ:
|
||||
return "HTTP_STATUS_PAYMENT_REQ";
|
||||
case HTTP_STATUS_FORBIDDEN:
|
||||
return "HTTP_STATUS_FORBIDDEN";
|
||||
case HTTP_STATUS_NOT_FOUND:
|
||||
return "HTTP_STATUS_NOT_FOUND";
|
||||
case HTTP_STATUS_BAD_METHOD:
|
||||
return "HTTP_STATUS_BAD_METHOD";
|
||||
case HTTP_STATUS_NONE_ACCEPTABLE:
|
||||
return "HTTP_STATUS_NONE_ACCEPTABLE";
|
||||
case HTTP_STATUS_PROXY_AUTH_REQ:
|
||||
return "HTTP_STATUS_PROXY_AUTH_REQ";
|
||||
case HTTP_STATUS_REQUEST_TIMEOUT:
|
||||
return "HTTP_STATUS_REQUEST_TIMEOUT";
|
||||
case HTTP_STATUS_CONFLICT:
|
||||
return "HTTP_STATUS_CONFLICT";
|
||||
case HTTP_STATUS_GONE:
|
||||
return "HTTP_STATUS_GONE";
|
||||
case HTTP_STATUS_LENGTH_REQUIRED:
|
||||
return "HTTP_STATUS_LENGTH_REQUIRED";
|
||||
case HTTP_STATUS_PRECOND_FAILED:
|
||||
return "HTTP_STATUS_PRECOND_FAILED";
|
||||
case HTTP_STATUS_REQUEST_TOO_LARGE:
|
||||
return "HTTP_STATUS_REQUEST_TOO_LARGE";
|
||||
case HTTP_STATUS_URI_TOO_LONG:
|
||||
return "HTTP_STATUS_URI_TOO_LONG";
|
||||
case HTTP_STATUS_UNSUPPORTED_MEDIA:
|
||||
return "HTTP_STATUS_UNSUPPORTED_MEDIA";
|
||||
case HTTP_STATUS_RETRY_WITH:
|
||||
return "HTTP_STATUS_RETRY_WITH";
|
||||
case HTTP_STATUS_SERVER_ERROR:
|
||||
return "HTTP_STATUS_SERVER_ERROR";
|
||||
case HTTP_STATUS_NOT_SUPPORTED:
|
||||
return "HTTP_STATUS_NOT_SUPPORTED";
|
||||
case HTTP_STATUS_BAD_GATEWAY:
|
||||
return "HTTP_STATUS_BAD_GATEWAY";
|
||||
case HTTP_STATUS_SERVICE_UNAVAIL:
|
||||
return "HTTP_STATUS_SERVICE_UNAVAIL";
|
||||
case HTTP_STATUS_GATEWAY_TIMEOUT:
|
||||
return "HTTP_STATUS_GATEWAY_TIMEOUT";
|
||||
case HTTP_STATUS_VERSION_NOT_SUP:
|
||||
return "HTTP_STATUS_VERSION_NOT_SUP";
|
||||
default:
|
||||
return "HTTP_STATUS_UNKNOWN";
|
||||
}
|
||||
}
|
||||
|
||||
const char* freerdp_http_status_string_format(long status, char* buffer, size_t size)
|
||||
{
|
||||
const char* code = freerdp_http_status_string(status);
|
||||
(void)_snprintf(buffer, size, "%s [%ld]", code, status);
|
||||
return buffer;
|
||||
}
|
||||
Reference in New Issue
Block a user