From 44b1c9fde4f1c6227f9b8a1f33817634667f8885 Mon Sep 17 00:00:00 2001 From: Billy O'Neal Date: Wed, 13 Jan 2021 14:05:38 -0800 Subject: [PATCH] [vcpkg] Add sources for TLS 1.2 downloader tool. (#15516) --- CMakeLists.txt | 23 +++- src/tls12-download.c | 311 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 332 insertions(+), 2 deletions(-) create mode 100644 src/tls12-download.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 529d797060065e..3f0c55cfffb57b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,11 @@ +if(WIN32) +# 3.16 for MSVC_RUNTIME_LIBRARY +cmake_minimum_required(VERSION 3.16) +else() cmake_minimum_required(VERSION 3.14) +endif() -project(vcpkg CXX) +project(vcpkg C CXX) include(cmake/utilities.cmake) # =============== @@ -178,6 +183,18 @@ if(VCPKG_BUILD_FUZZING) vcpkg_target_add_warning_options(vcpkg-fuzz) endif() + +# === Target: tls12-download === + +set(TLS12_DOWNLOAD_SOURCES src/tls12-download.c) +if(WIN32) + add_executable(tls12-download ${TLS12_DOWNLOAD_SOURCES}) + set_property(TARGET tls12-download PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded") + set_property(TARGET tls12-download APPEND PROPERTY LINK_OPTIONS "$,,/ENTRY:entry>") + target_link_libraries(tls12-download winhttp) +endif() + + # === Target: format === find_program(CLANG_FORMAT clang-format) @@ -196,5 +213,7 @@ if(CLANG_FORMAT) COMMAND ${CLANG_FORMAT} -i -verbose ${VCPKG_TEST_SOURCES} COMMAND ${CLANG_FORMAT} -i -verbose ${VCPKG_TEST_INCLUDES} - COMMAND ${CLANG_FORMAT} -i -verbose ${VCPKG_FUZZ_SOURCES}) + COMMAND ${CLANG_FORMAT} -i -verbose ${VCPKG_FUZZ_SOURCES} + COMMAND ${CLANG_FORMAT} -i -verbose ${TLS12_DOWNLOAD_SOURCES} + ) endif() diff --git a/src/tls12-download.c b/src/tls12-download.c new file mode 100644 index 00000000000000..16edc544845ad7 --- /dev/null +++ b/src/tls12-download.c @@ -0,0 +1,311 @@ +#include +#include +#include +/* + * This program must be as small as possible, because it is committed in binary form to the + * vcpkg github repo to enable downloading the main vcpkg program on Windows 7, where TLS 1.2 is + * unavailable to PowerShell. + * To that end it avoids using C runtime functions (beyond the vcruntime ones the compiler + * injects itself). + * (In testing as of 2021-01-07, this version that doesn't link with the CRT is ~8kb, whereas a + * hello world program that does link with the CRT is ~300kb) + */ + +static void __declspec(noreturn) win32_abort() +{ + /* + * Note that TerminateProcess does not return when called from the terminated process, see + * https://github.com/MicrosoftDocs/sdk-api/pull/626 + */ + TerminateProcess(GetCurrentProcess(), 3); +} + +static size_t wide_length(const wchar_t* str) +{ + size_t answer = 0; + while (*str) + { + ++answer; + ++str; + } + return answer; +} + +static void write_message(const HANDLE std_out, const wchar_t* msg) +{ + size_t wchars_to_write = wide_length(msg); + if (wchars_to_write == 0) + { + return; + } + + if (wchars_to_write > 65535) + { + win32_abort(); + } + + if (WriteConsoleW(std_out, msg, wchars_to_write, 0, 0)) + { + return; + } + + // this happens if output has been redirected + int narrow_chars = WideCharToMultiByte(CP_ACP, 0, msg, (int)wchars_to_write, 0, 0, 0, 0); + if (narrow_chars == 0) + { + win32_abort(); + } + + char* narrow_buffer = HeapAlloc(GetProcessHeap(), 0, (size_t)narrow_chars); + if (WideCharToMultiByte(CP_ACP, 0, msg, (int)wchars_to_write, narrow_buffer, narrow_chars, 0, 0) == 0) + { + win32_abort(); + } + + while (narrow_chars != 0) + { + DWORD chars_written; + if (!WriteFile(std_out, narrow_buffer, (DWORD)narrow_chars, &chars_written, 0)) + { + win32_abort(); + } + + narrow_chars -= (int)chars_written; + } + + if (!HeapFree(GetProcessHeap(), 0, narrow_buffer)) + { + win32_abort(); + } +} + +static void write_number(const HANDLE std_out, DWORD number) +{ + wchar_t buffer[11]; // 4294967295\0 + wchar_t* first_digit = buffer + 11; + *--first_digit = L'\0'; + if (number == 0) + { + *--first_digit = L'0'; + } + else + { + do + { + *--first_digit = L'0' + number % 10; + number /= 10; + } while (number != 0); + } + + write_message(std_out, first_digit); +} + +static void write_hex(const HANDLE std_out, DWORD number) +{ + wchar_t buffer[] = L"0x00000000"; + wchar_t* first_digit = buffer + (sizeof(buffer) / sizeof(wchar_t)) - 1; + while (number != 0) + { + *--first_digit = L"0123456789ABCDEF"[number % 16]; + number /= 16; + } + + write_message(std_out, buffer); +} + +static void __declspec(noreturn) abort_api_failure(const HANDLE std_out, const wchar_t* api_name) +{ + DWORD last_error = GetLastError(); + write_message(std_out, L"While calling Windows API function "); + write_message(std_out, api_name); + write_message(std_out, L" got error "); + write_hex(std_out, last_error); + write_message(std_out, L":\r\n"); + wchar_t* message; + if (FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_FROM_HMODULE | FORMAT_MESSAGE_ALLOCATE_BUFFER, + GetModuleHandleW(L"winhttp.dll"), + last_error, + 0, + (LPWSTR)&message, + 0, + 0)) + { + write_message(std_out, message); + // intentionally leaks the message buffer + } + else + { + last_error = GetLastError(); + write_message(std_out, L"(unknown error, FormatMessageW failed with "); + write_hex(std_out, last_error); + write_message(std_out, L")"); + } + + write_message(std_out, L"\r\n"); + FlushFileBuffers(std_out); + win32_abort(); +} + +#ifndef NDEBUG +int main() +#else // ^^^ debug // !debug vvv +int __stdcall entry() +#endif // ^^^ !debug +{ +#ifdef NDEBUG + __security_init_cookie(); +#endif // ^^^ release + + const HANDLE std_out = GetStdHandle(STD_OUTPUT_HANDLE); + if (std_out == INVALID_HANDLE_VALUE) + { + win32_abort(); + } + + int argc; + wchar_t** argv = CommandLineToArgvW(GetCommandLineW(), &argc); // intentionally leaks argv + if (argv == 0) + { + win32_abort(); + } + + if (argc != 4) + { + write_message(std_out, L"Usage: tls12-download.exe DOMAIN RELATIVE-PATH OUT-FILE\r\n"); + return 1; + } + + const wchar_t* const domain = argv[1]; + const wchar_t* const relative_path = argv[2]; + const wchar_t* const out_file_path = argv[3]; + write_message(std_out, L"Downloading https://"); + write_message(std_out, domain); + write_message(std_out, relative_path); + write_message(std_out, L" -> "); + write_message(std_out, out_file_path); + + wchar_t https_proxy_env[32767]; + DWORD access_type; + const wchar_t* proxy_setting; + const wchar_t* proxy_bypass_setting; + if (GetEnvironmentVariableW(L"HTTPS_PROXY", https_proxy_env, sizeof(https_proxy_env) / sizeof(wchar_t))) + { + access_type = WINHTTP_ACCESS_TYPE_NAMED_PROXY; + proxy_setting = https_proxy_env; + proxy_bypass_setting = L""; + write_message(std_out, L" (using proxy: "); + write_message(std_out, proxy_setting); + write_message(std_out, L")"); + } + else if (GetLastError() == ERROR_ENVVAR_NOT_FOUND) + { + access_type = WINHTTP_ACCESS_TYPE_NO_PROXY; + proxy_setting = WINHTTP_NO_PROXY_NAME; + proxy_bypass_setting = WINHTTP_NO_PROXY_BYPASS; + } + else + { + abort_api_failure(std_out, L"GetEnvironmentVariableW"); + } + + write_message(std_out, L"\r\n"); + + const HANDLE out_file = CreateFileW(out_file_path, FILE_WRITE_DATA, 0, 0, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0); + if (out_file == INVALID_HANDLE_VALUE) + { + abort_api_failure(std_out, L"CreateFileW"); + } + + BOOL results = FALSE; + const HINTERNET session = WinHttpOpen(L"tls12-download/1.0", access_type, proxy_setting, proxy_bypass_setting, 0); + if (!session) + { + abort_api_failure(std_out, L"WinHttpOpen"); + } + + unsigned long secure_protocols = WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_2; + if (!WinHttpSetOption(session, WINHTTP_OPTION_SECURE_PROTOCOLS, &secure_protocols, sizeof(DWORD))) + { + abort_api_failure(std_out, L"WinHttpSetOption"); + } + + const HINTERNET connect = WinHttpConnect(session, domain, INTERNET_DEFAULT_HTTPS_PORT, 0); + if (!connect) + { + abort_api_failure(std_out, L"WinHttpConnect"); + } + + const HINTERNET request = WinHttpOpenRequest( + connect, L"GET", relative_path, 0, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, WINHTTP_FLAG_SECURE); + if (!request) + { + abort_api_failure(std_out, L"WinHttpOpenRequest"); + } + + if (!WinHttpSendRequest(request, WINHTTP_NO_ADDITIONAL_HEADERS, 0, WINHTTP_NO_REQUEST_DATA, 0, 0, 0)) + { + abort_api_failure(std_out, L"WinHttpSendRequest"); + } + + if (!WinHttpReceiveResponse(request, 0)) + { + abort_api_failure(std_out, L"WinHttpReceiveResponse"); + } + + DWORD http_code = 0; + DWORD query_headers_buffer_size = sizeof(http_code); + if (!WinHttpQueryHeaders(request, + WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER, + WINHTTP_HEADER_NAME_BY_INDEX, + &http_code, + &query_headers_buffer_size, + WINHTTP_NO_HEADER_INDEX)) + { + abort_api_failure(std_out, L"WinHttpQueryHeaders"); + } + + if (http_code != 200) + { + write_message(std_out, L"Download failed, server returned HTTP status: "); + write_number(std_out, http_code); + write_message(std_out, L"\r\n"); + FlushFileBuffers(std_out); + TerminateProcess(GetCurrentProcess(), 2); + } + + char buffer[32768]; + for (;;) + { + DWORD received_bytes; + if (!WinHttpReadData(request, buffer, sizeof(buffer), &received_bytes)) + { + abort_api_failure(std_out, L"WinHttpReadData"); + } + + if (received_bytes == 0) + { + break; // end of response + } + + do + { + DWORD written_bytes; + if (!WriteFile(out_file, buffer, received_bytes, &written_bytes, 0)) + { + abort_api_failure(std_out, L"WriteFile"); + } + + received_bytes -= written_bytes; + } while (received_bytes != 0); + } + + WinHttpCloseHandle(request); + WinHttpCloseHandle(connect); + WinHttpCloseHandle(session); + CloseHandle(out_file); + + write_message(std_out, L"Done.\r\n"); + FlushFileBuffers(std_out); + TerminateProcess(GetCurrentProcess(), 0); + return 0; +}