#if defined(_WIN32)
#include <winsock2.h>
#include <ws2tcpip.h>
#include <windows.h>
#include <winhttp.h>
#include <intrin.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "hymma/hlm.h"
#include "hlm_sntp.h"
#pragma comment(lib, "winhttp.lib")
#pragma comment(lib, "ws2_32.lib")
#pragma comment(lib, "advapi32.lib")
#define HLM_HTTP_TIMEOUT_MS 15000
static int utf8_to_wide(const char *s, wchar_t *out, int cap)
{
int n = MultiByteToWideChar(CP_UTF8, 0, s, -1, out, cap);
return n > 0 ? 0 : -1;
}
static int append_header(wchar_t *buf, int cap, int *pos, const wchar_t *fmt,
const wchar_t *val)
{
int n = _snwprintf(buf + *pos, (size_t)(cap - *pos), fmt, val);
if (n < 0 || n >= cap - *pos) {
buf[*pos] = L'\0';
return -1;
}
*pos += n;
return 0;
}
static HINTERNET open_session(void)
{
HINTERNET s = WinHttpOpen(L"license-management-core/1.0",
WINHTTP_ACCESS_TYPE_AUTOMATIC_PROXY,
WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0);
if (s == NULL && GetLastError() == ERROR_INVALID_PARAMETER)
s = WinHttpOpen(L"license-management-core/1.0",
WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,
WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0);
if (s != NULL)
WinHttpSetTimeouts(s, HLM_HTTP_TIMEOUT_MS, HLM_HTTP_TIMEOUT_MS,
HLM_HTTP_TIMEOUT_MS, HLM_HTTP_TIMEOUT_MS);
return s;
}
static int arm_proxy_default_credentials(HINTERNET request)
{
DWORD supported = 0, first = 0, target = 0, scheme = 0;
DWORD policy = WINHTTP_AUTOLOGON_SECURITY_LEVEL_LOW;
if (!WinHttpQueryAuthSchemes(request, &supported, &first, &target) ||
target != WINHTTP_AUTH_TARGET_PROXY)
return 0;
if (!WinHttpSetOption(request, WINHTTP_OPTION_AUTOLOGON_POLICY, &policy,
sizeof(policy)))
return 0;
if (supported & WINHTTP_AUTH_SCHEME_NEGOTIATE)
scheme = WINHTTP_AUTH_SCHEME_NEGOTIATE;
else if (supported & WINHTTP_AUTH_SCHEME_NTLM)
scheme = WINHTTP_AUTH_SCHEME_NTLM;
else
return 0;
return WinHttpSetCredentials(request, WINHTTP_AUTH_TARGET_PROXY, scheme,
NULL, NULL, NULL) != FALSE;
}
static int winhttp_send(void *user, const hlm_http_request *req,
hlm_http_response *resp)
{
hlm_http_cache *cache = (hlm_http_cache *)user;
HINTERNET session = NULL, connect = NULL, request = NULL;
wchar_t wurl[1024], whost[256], wpath[768], wmethod[16];
wchar_t headers[1024];
URL_COMPONENTS uc;
int result = HLM_E_HTTP;
DWORD status = 0, status_len = sizeof(status);
size_t total = 0;
int proxy_auth_tried = 0;
resp->retry_after_seconds = -1;
if (utf8_to_wide(req->url, wurl,
(int)(sizeof(wurl) / sizeof(wurl[0]))) < 0 ||
utf8_to_wide(req->method, wmethod,
(int)(sizeof(wmethod) / sizeof(wmethod[0]))) < 0)
return HLM_E_ARG;
memset(&uc, 0, sizeof(uc));
uc.dwStructSize = sizeof(uc);
uc.lpszHostName = whost;
uc.dwHostNameLength = (DWORD)(sizeof(whost) / sizeof(whost[0]));
uc.lpszUrlPath = wpath;
uc.dwUrlPathLength = (DWORD)(sizeof(wpath) / sizeof(wpath[0]));
if (!WinHttpCrackUrl(wurl, 0, 0, &uc)) return HLM_E_ARG;
if (cache != NULL && cache->h != NULL) {
session = (HINTERNET)cache->h;
} else {
session = open_session();
if (session == NULL) goto done;
if (cache != NULL) cache->h = session;
}
connect = WinHttpConnect(session, whost, uc.nPort, 0);
if (connect == NULL) goto done;
request = WinHttpOpenRequest(
connect, wmethod, wpath, NULL, WINHTTP_NO_REFERER,
WINHTTP_DEFAULT_ACCEPT_TYPES,
uc.nScheme == INTERNET_SCHEME_HTTPS ? WINHTTP_FLAG_SECURE : 0);
if (request == NULL) goto done;
{
wchar_t wkey[256], wcorr[64], widem[300];
const int hcap = (int)(sizeof(headers) / sizeof(headers[0]));
int hl = 0;
headers[0] = L'\0';
if (req->api_key != NULL &&
(utf8_to_wide(req->api_key, wkey,
(int)(sizeof(wkey) / sizeof(wkey[0]))) < 0 ||
append_header(headers, hcap, &hl, L"X-API-KEY: %s\r\n", wkey) < 0)) {
result = HLM_E_ARG;
goto done;
}
if (req->correlation_id != NULL &&
(utf8_to_wide(req->correlation_id, wcorr,
(int)(sizeof(wcorr) / sizeof(wcorr[0]))) < 0 ||
append_header(headers, hcap, &hl,
L"X-Correlation-Id: %s\r\n", wcorr) < 0)) {
result = HLM_E_ARG;
goto done;
}
if (req->idempotency_key != NULL &&
(utf8_to_wide(req->idempotency_key, widem,
(int)(sizeof(widem) / sizeof(widem[0]))) < 0 ||
append_header(headers, hcap, &hl,
L"Idempotency-Key: %s\r\n", widem) < 0)) {
result = HLM_E_ARG;
goto done;
}
if (req->body != NULL &&
append_header(headers, hcap, &hl,
L"Content-Type: application/json\r\n", NULL) < 0) {
result = HLM_E_ARG;
goto done;
}
}
for (;;) {
if (!WinHttpSendRequest(request,
headers[0] != L'\0' ? headers : WINHTTP_NO_ADDITIONAL_HEADERS,
(DWORD)-1,
req->body != NULL ? (LPVOID)req->body : WINHTTP_NO_REQUEST_DATA,
req->body != NULL ? (DWORD)strlen(req->body) : 0,
req->body != NULL ? (DWORD)strlen(req->body) : 0,
0))
goto done;
if (!WinHttpReceiveResponse(request, NULL)) goto done;
status_len = sizeof(status);
if (!WinHttpQueryHeaders(request,
WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER,
WINHTTP_HEADER_NAME_BY_INDEX, &status, &status_len,
WINHTTP_NO_HEADER_INDEX))
goto done;
if (status == 407 && !proxy_auth_tried) {
proxy_auth_tried = 1;
if (arm_proxy_default_credentials(request)) continue;
}
break;
}
{
wchar_t ra[64];
DWORD ra_len = sizeof(ra);
if (WinHttpQueryHeaders(request, WINHTTP_QUERY_RETRY_AFTER,
WINHTTP_HEADER_NAME_BY_INDEX, ra, &ra_len,
WINHTTP_NO_HEADER_INDEX)) {
long v = wcstol(ra, NULL, 10);
if (v >= 0 && v < 24 * 3600) resp->retry_after_seconds = (int)v;
}
}
for (;;) {
DWORD avail = 0, got = 0;
if (!WinHttpQueryDataAvailable(request, &avail)) goto done;
if (avail == 0) break;
if (total + avail > resp->body_cap) {
result = HLM_E_BUFFER;
goto done;
}
if (!WinHttpReadData(request, resp->body + total, avail, &got)) goto done;
total += got;
}
resp->status = (int)status;
resp->body_len = total;
result = HLM_OK;
done:
if (result == HLM_E_HTTP)
resp->failure = hlm_net_failure_from_winhttp(GetLastError());
if (request) WinHttpCloseHandle(request);
if (connect) WinHttpCloseHandle(connect);
if (session && cache == NULL) WinHttpCloseHandle(session);
return result;
}
hlm_http hlm_http_winhttp(void)
{
hlm_http h;
h.send = winhttp_send;
h.user = 0;
return h;
}
hlm_http hlm_http_winhttp_cached(hlm_http_cache *cache)
{
hlm_http h;
h.send = winhttp_send;
h.user = cache;
return h;
}
void hlm_http_cache_close(hlm_http_cache *cache)
{
if (cache == NULL || cache->h == NULL) return;
WinHttpCloseHandle((HINTERNET)cache->h);
cache->h = NULL;
}
static void sleep_win(void *user, unsigned ms)
{
(void)user;
Sleep(ms);
}
hlm_sleep hlm_sleep_win(void)
{
hlm_sleep s;
s.sleep_ms = sleep_win;
s.user = 0;
return s;
}
static INIT_ONCE g_wsa_once = INIT_ONCE_STATIC_INIT;
static int g_wsa_ok = 0;
static BOOL CALLBACK wsa_init_once(PINIT_ONCE once, PVOID param, PVOID *ctx)
{
WSADATA wsa;
(void)once; (void)param; (void)ctx;
g_wsa_ok = WSAStartup(MAKEWORD(2, 2), &wsa) == 0;
return TRUE;
}
static int sntp_query(const char *host, int timeout_ms, int64_t *epoch_out)
{
struct addrinfo hints, *ai = NULL;
SOCKET sock = INVALID_SOCKET;
uint8_t pkt[HLM_SNTP_PACKET_SIZE];
int result = -1;
InitOnceExecuteOnce(&g_wsa_once, wsa_init_once, NULL, NULL);
if (!g_wsa_ok) return -1;
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_DGRAM;
hints.ai_protocol = IPPROTO_UDP;
if (getaddrinfo(host, "123", &hints, &ai) != 0 || ai == NULL) goto done;
sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
if (sock == INVALID_SOCKET) goto done;
{
DWORD tmo = (DWORD)timeout_ms;
setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (const char *)&tmo, sizeof(tmo));
setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, (const char *)&tmo, sizeof(tmo));
}
hlm_sntp_build_request(pkt);
if (sendto(sock, (const char *)pkt, sizeof(pkt), 0, ai->ai_addr,
(int)ai->ai_addrlen) != sizeof(pkt))
goto done;
if (recv(sock, (char *)pkt, sizeof(pkt), 0) != sizeof(pkt)) goto done;
if (hlm_sntp_parse_reply(pkt, epoch_out) != 0) goto done;
result = 0;
done:
if (sock != INVALID_SOCKET) closesocket(sock);
if (ai != NULL) freeaddrinfo(ai);
return result;
}
static int w32time_running(void)
{
SC_HANDLE scm, svc;
SERVICE_STATUS ss;
int running = 0;
scm = OpenSCManagerA(NULL, NULL, SC_MANAGER_CONNECT);
if (scm == NULL) return 0;
svc = OpenServiceA(scm, "w32time", SERVICE_QUERY_STATUS);
if (svc != NULL) {
if (QueryServiceStatus(svc, &ss))
running = ss.dwCurrentState == SERVICE_RUNNING;
CloseServiceHandle(svc);
}
CloseServiceHandle(scm);
return running;
}
static int w32time_uses_ntp(void)
{
char type[32];
DWORD len = sizeof(type);
if (RegGetValueA(HKEY_LOCAL_MACHINE,
"SYSTEM\\CurrentControlSet\\Services\\W32Time\\Parameters",
"Type", RRF_RT_REG_SZ, NULL, type, &len) != ERROR_SUCCESS)
return 0;
return _stricmp(type, "NTP") == 0;
}
static int64_t local_utc_now(void)
{
FILETIME ft;
ULARGE_INTEGER u;
GetSystemTimeAsFileTime(&ft);
u.LowPart = ft.dwLowDateTime;
u.HighPart = ft.dwHighDateTime;
return (int64_t)(u.QuadPart / 10000000ULL) - 11644473600LL;
}
static int timesync_win_now(void *user, int64_t *now_utc)
{
int64_t ntp_time;
(void)user;
if (w32time_running() && w32time_uses_ntp() &&
sntp_query("time.windows.com", HLM_NTP_TIMEOUT_MS, &ntp_time) == 0) {
int64_t local = local_utc_now();
int64_t drift = local - ntp_time;
if (drift < 0) drift = -drift;
if (drift < HLM_MAX_DRIFT_SECONDS) {
*now_utc = local;
return HLM_OK;
}
*now_utc = ntp_time;
return HLM_OK;
}
if (sntp_query("pool.ntp.org", HLM_NTP_TIMEOUT_MS, &ntp_time) == 0) {
*now_utc = ntp_time;
return HLM_OK;
}
return HLM_E_HTTP;
}
hlm_timesync hlm_timesync_win(void)
{
hlm_timesync t;
t.now = timesync_win_now;
t.user = 0;
return t;
}
#define HLM_FIRMWARE_RSMB 0x52534D42u
typedef struct {
BYTE Used20CallingMethod;
BYTE SMBIOSMajorVersion;
BYTE SMBIOSMinorVersion;
BYTE DmiRevision;
DWORD Length;
BYTE SMBIOSTableData[1];
} raw_smbios;
static const char *smbios_string(const BYTE *strings, const BYTE *end, int index)
{
int i;
const BYTE *p = strings;
if (index <= 0) return NULL;
for (i = 1; p < end; i++) {
size_t len = strnlen((const char *)p, (size_t)(end - p));
if (len == (size_t)(end - p)) return NULL;
if (len == 0) return NULL;
if (i == index) return (const char *)p;
p += len + 1;
}
return NULL;
}
static int smbios_baseboard_serial(char *out, size_t out_len)
{
UINT size;
BYTE *buf;
raw_smbios *smb;
const BYTE *p, *end;
int found = 0;
size = GetSystemFirmwareTable(HLM_FIRMWARE_RSMB, 0, NULL, 0);
if (size <= offsetof(raw_smbios, SMBIOSTableData) || size > 1024 * 1024)
return -1;
buf = (BYTE *)HeapAlloc(GetProcessHeap(), 0, size);
if (buf == NULL) return -1;
if (GetSystemFirmwareTable(HLM_FIRMWARE_RSMB, 0, buf, size) != size)
goto done;
smb = (raw_smbios *)buf;
p = smb->SMBIOSTableData;
{
size_t cap = size - offsetof(raw_smbios, SMBIOSTableData);
end = p + (smb->Length <= cap ? smb->Length : cap);
}
while (p + 4 <= end) {
BYTE type = p[0];
BYTE len = p[1];
const BYTE *strings;
const BYTE *q;
if (len < 4 || len > (size_t)(end - p)) break;
strings = p + len;
q = strings;
while (q + 1 < end && !(q[0] == 0 && q[1] == 0)) q++;
if (q + 1 >= end) break;
q += 2;
if (type == 2 && len > 0x07) {
const char *serial = smbios_string(strings, end, p[0x07]);
if (serial != NULL && serial[0] != '\0') {
size_t sl = strlen(serial);
if (sl < out_len) {
memcpy(out, serial, sl + 1);
found = 1;
}
}
break;
}
p = q;
}
done:
HeapFree(GetProcessHeap(), 0, buf);
return found ? 0 : -1;
}
static void processor_id(char out[17])
{
int regs[4] = { 0, 0, 0, 0 };
__cpuid(regs, 1);
snprintf(out, 17, "%08X%08X", (unsigned)regs[3], (unsigned)regs[0]);
}
int hlm_machine_id_win(char *out, size_t out_len)
{
char serial[128];
char cpu[17];
const char *components[2];
if (smbios_baseboard_serial(serial, sizeof(serial)) != 0)
return HLM_E_ARG;
processor_id(cpu);
components[0] = serial;
components[1] = cpu;
return hlm_fingerprint(components, 2, out, out_len);
}
static int registry_machine_guid(char *out, size_t out_len)
{
HKEY key;
DWORD type = 0, sz;
if (out_len < 2) return -1;
if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Cryptography",
0, KEY_QUERY_VALUE | KEY_WOW64_64KEY, &key) !=
ERROR_SUCCESS)
return -1;
sz = (DWORD)(out_len - 1);
if (RegQueryValueExA(key, "MachineGuid", NULL, &type, (LPBYTE)out, &sz) !=
ERROR_SUCCESS ||
type != REG_SZ) {
RegCloseKey(key);
return -1;
}
RegCloseKey(key);
out[sz] = '\0';
return strlen(out) >= 8 ? 0 : -1;
}
int hlm_machine_id_win_v2(char *out, size_t out_len)
{
char guid[128];
const char *components[1];
if (registry_machine_guid(guid, sizeof(guid)) != 0)
return HLM_E_ARG;
components[0] = guid;
return hlm_fingerprint(components, 1, out, out_len);
}
int hlm_machine_name_win(char *out, size_t out_len)
{
DWORD n = (DWORD)out_len;
if (!GetComputerNameA(out, &n)) return HLM_E_ARG;
return HLM_OK;
}
#endif