diff --git a/appveyor.yml b/appveyor.yml index 05dbd53..f486b69 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -38,11 +38,16 @@ for: matrix: only: - ruby_version: "31-x64" + - ruby_version: "27-x64" + - ruby_version: "27" + - ruby_version: "26-x64" + - ruby_version: "26" install: + - ps: if ($ENV:ruby_version -ne "31-x64") { .\ruby_install.ps1 } - SET PATH=C:\Ruby%ruby_version%\bin;%PATH% - ruby --version - gem --version - bundle --version - - ridk.cmd install 1 3 + - ps: if ($ENV:ruby_version -eq "31-x64") { ridk.ps1 install 1 3 } - ridk.cmd exec bundle install - ridk.cmd exec bundle exec rake compile diff --git a/ext/winevt/extconf.rb b/ext/winevt/extconf.rb index 2201c62..4cead4f 100644 --- a/ext/winevt/extconf.rb +++ b/ext/winevt/extconf.rb @@ -14,6 +14,9 @@ have_func("EvtQuery", "winevt.h") have_library("advapi32") have_library("ole32") +if have_macro("RB_ALLOCV") + $CFLAGS << " -DHAVE_RB_ALLOCV=1 " +end $LDFLAGS << " -lwevtapi -ladvapi32 -lole32" $CFLAGS << " -Wall -std=c99 -fPIC -fms-extensions " diff --git a/ext/winevt/winevt_c.h b/ext/winevt/winevt_c.h index baafc5e..2d1892a 100644 --- a/ext/winevt/winevt_c.h +++ b/ext/winevt/winevt_c.h @@ -16,6 +16,11 @@ #endif /* WIN32_WINNT */ #define _WIN32_WINNT MINIMUM_WINDOWS_VERSION +#if !defined(HAVE_RB_ALLOCV) +#define ALLOCV RB_ALLOCV +#define ALLOCV_N RB_ALLOCV_N +#endif + #include #include #define EventQuery(object) ((struct WinevtQuery*)DATA_PTR(object)) @@ -33,6 +38,9 @@ typedef struct { extern "C" { #endif /* __cplusplus */ +#define WINEVT_UTILS_ERROR_NONE_MAPPED -1 +#define WINEVT_UTILS_ERROR_OTHERS -2 + VALUE wstr_to_rb_str(UINT cp, const WCHAR* wstr, int clen); #if defined(__cplusplus) [[ noreturn ]] @@ -46,7 +54,7 @@ EVT_HANDLE connect_to_remote(LPWSTR computerName, LPWSTR domain, DWORD *error_code); WCHAR* get_description(EVT_HANDLE handle, LANGID langID, EVT_HANDLE hRemote); VALUE get_values(EVT_HANDLE handle); -VALUE render_system_event(EVT_HANDLE handle, BOOL preserve_qualifiers); +VALUE render_system_event(EVT_HANDLE handle, BOOL preserve_qualifiers, BOOL preserveSID); LocaleInfo* get_locale_info_from_rb_str(VALUE rb_locale_str); #ifdef __cplusplus @@ -101,6 +109,7 @@ struct WinevtQuery LONG timeout; BOOL renderAsXML; BOOL preserveQualifiers; + BOOL preserveSID; LocaleInfo *localeInfo; EVT_HANDLE remoteHandle; }; @@ -122,6 +131,7 @@ struct WinevtSubscribe DWORD currentRate; BOOL renderAsXML; BOOL preserveQualifiers; + BOOL preserveSID; LocaleInfo* localeInfo; EVT_HANDLE remoteHandle; }; diff --git a/ext/winevt/winevt_query.c b/ext/winevt/winevt_query.c index 8778eb1..7818377 100644 --- a/ext/winevt/winevt_query.c +++ b/ext/winevt/winevt_query.c @@ -153,6 +153,7 @@ rb_winevt_query_initialize(VALUE argc, VALUE *argv, VALUE self) winevtQuery->preserveQualifiers = FALSE; winevtQuery->localeInfo = &default_locale; winevtQuery->remoteHandle = hRemoteHandle; + winevtQuery->preserveSID = TRUE; ALLOCV_END(wchannelBuf); ALLOCV_END(wpathBuf); @@ -274,7 +275,8 @@ rb_winevt_query_render(VALUE self, EVT_HANDLE event) if (winevtQuery->renderAsXML) { return render_to_rb_str(event, EvtRenderEventXml); } else { - return render_system_event(event, winevtQuery->preserveQualifiers); + return render_system_event(event, winevtQuery->preserveQualifiers, + winevtQuery->preserveSID); } } @@ -535,6 +537,40 @@ rb_winevt_query_get_locale(VALUE self) } } +/* + * This method specifies whether preserving SID or not. + * + * @param rb_preserve_sid_p [Boolean] + */ +static VALUE +rb_winevt_query_set_preserve_sid(VALUE self, VALUE rb_preserve_sid_p) +{ + struct WinevtQuery* winevtQuery; + + TypedData_Get_Struct( + self, struct WinevtQuery, &rb_winevt_query_type, winevtQuery); + + winevtQuery->preserveSID = RTEST(rb_preserve_sid_p); + + return Qnil; +} + +/* + * This method returns whether preserving SID or not. + * + * @return [Boolean] + */ +static VALUE +rb_winevt_query_preserve_sid_p(VALUE self) +{ + struct WinevtQuery* winevtQuery; + + TypedData_Get_Struct( + self, struct WinevtQuery, &rb_winevt_query_type, winevtQuery); + + return winevtQuery->preserveSID ? Qtrue : Qfalse; +} + /* * This method cancels channel query. * @@ -683,6 +719,14 @@ Init_winevt_query(VALUE rb_cEventLog) * @since 0.8.0 */ rb_define_method(rb_cQuery, "locale=", rb_winevt_query_set_locale, 1); + /* + * @since 0.10.3 + */ + rb_define_method(rb_cQuery, "preserve_sid?", rb_winevt_query_preserve_sid_p, 0); + /* + * @since 0.10.3 + */ + rb_define_method(rb_cQuery, "preserve_sid=", rb_winevt_query_set_preserve_sid, 1); /* * @since 0.9.1 */ diff --git a/ext/winevt/winevt_subscribe.c b/ext/winevt/winevt_subscribe.c index b8846f2..3358a42 100644 --- a/ext/winevt/winevt_subscribe.c +++ b/ext/winevt/winevt_subscribe.c @@ -110,6 +110,7 @@ rb_winevt_subscribe_initialize(VALUE self) winevtSubscribe->readExistingEvents = TRUE; winevtSubscribe->preserveQualifiers = FALSE; winevtSubscribe->localeInfo = &default_locale; + winevtSubscribe->preserveSID = TRUE; return Qnil; } @@ -417,7 +418,8 @@ rb_winevt_subscribe_render(VALUE self, EVT_HANDLE event) if (winevtSubscribe->renderAsXML) { return render_to_rb_str(event, EvtRenderEventXml); } else { - return render_system_event(event, winevtSubscribe->preserveQualifiers); + return render_system_event(event, winevtSubscribe->preserveQualifiers, + winevtSubscribe->preserveSID); } } @@ -674,6 +676,40 @@ rb_winevt_subscribe_get_locale(VALUE self) } } +/* + * This method specifies whether preserving SID or not. + * + * @param rb_preserve_sid_p [Boolean] + */ +static VALUE +rb_winevt_subscribe_set_preserve_sid(VALUE self, VALUE rb_preserve_sid_p) +{ + struct WinevtSubscribe* winevtSubscribe; + + TypedData_Get_Struct( + self, struct WinevtSubscribe, &rb_winevt_subscribe_type, winevtSubscribe); + + winevtSubscribe->preserveSID = RTEST(rb_preserve_sid_p); + + return Qnil; +} + +/* + * This method returns whether preserving SID or not. + * + * @return [Boolean] + */ +static VALUE +rb_winevt_subscribe_preserve_sid_p(VALUE self) +{ + struct WinevtSubscribe* winevtSubscribe; + + TypedData_Get_Struct( + self, struct WinevtSubscribe, &rb_winevt_subscribe_type, winevtSubscribe); + + return winevtSubscribe->preserveSID ? Qtrue : Qfalse; +} + /* * This method cancels channel subscription. * @@ -771,6 +807,14 @@ Init_winevt_subscribe(VALUE rb_cEventLog) */ rb_define_method( rb_cSubscribe, "locale=", rb_winevt_subscribe_set_locale, 1); + /* + * @since 0.10.3 + */ + rb_define_method(rb_cSubscribe, "preserve_sid?", rb_winevt_subscribe_preserve_sid_p, 0); + /* + * @since 0.10.3 + */ + rb_define_method(rb_cSubscribe, "preserve_sid=", rb_winevt_subscribe_set_preserve_sid, 1); /* * @since 0.9.1 */ diff --git a/ext/winevt/winevt_utils.cpp b/ext/winevt/winevt_utils.cpp index 7996b60..7a11789 100644 --- a/ext/winevt/winevt_utils.cpp +++ b/ext/winevt/winevt_utils.cpp @@ -17,7 +17,7 @@ wstr_to_rb_str(UINT cp, const WCHAR* wstr, int clen) } int len = WideCharToMultiByte(cp, 0, wstr, clen, nullptr, 0, nullptr, nullptr); - ptr = ALLOCV_N(CHAR, vstr, len); + ptr = RB_ALLOCV_N(CHAR, vstr, len); // For memory safety. ZeroMemory(ptr, sizeof(CHAR) * len); ret = WideCharToMultiByte(cp, 0, wstr, clen, ptr, len, nullptr, nullptr); @@ -25,11 +25,11 @@ wstr_to_rb_str(UINT cp, const WCHAR* wstr, int clen) // ref: https://docs.microsoft.com/en-us/windows/win32/api/stringapiset/nf-stringapiset-widechartomultibyte#return-value if (ret == 0) { err = GetLastError(); - ALLOCV_END(vstr); + RB_ALLOCV_END(vstr); raise_system_error(rb_eRuntimeError, err); } VALUE str = rb_utf8_str_new_cstr(ptr); - ALLOCV_END(vstr); + RB_ALLOCV_END(vstr); return str; } @@ -85,18 +85,18 @@ render_to_rb_str(EVT_HANDLE handle, DWORD flags) EvtRender(nullptr, handle, flags, 0, NULL, &bufferSize, &count); // bufferSize is in bytes, not characters - buffer = (WCHAR*)ALLOCV(vbuffer, bufferSize); + buffer = (WCHAR*)RB_ALLOCV(vbuffer, bufferSize); succeeded = EvtRender(nullptr, handle, flags, bufferSize, buffer, &bufferSizeUsed, &count); if (!succeeded) { DWORD status = GetLastError(); - ALLOCV_END(vbuffer); + RB_ALLOCV_END(vbuffer); raise_system_error(rb_eWinevtQueryError, status); } result = wstr_to_rb_str(CP_UTF8, buffer, -1); - ALLOCV_END(vbuffer); + RB_ALLOCV_END(vbuffer); return result; } @@ -153,7 +153,7 @@ make_displayable_binary_string(PBYTE bin, size_t length) return rb_str_new2("(NULL)"); } - buffer = ALLOCV_N(CHAR, vbuffer, size); + buffer = RB_ALLOCV_N(CHAR, vbuffer, size); for (i = 0; i < length; i++) { for (j = 0; j < 2; j++) { @@ -164,7 +164,7 @@ make_displayable_binary_string(PBYTE bin, size_t length) buffer[size - 1] = '\0'; VALUE str = rb_str_new2(buffer); - ALLOCV_END(vbuffer); + RB_ALLOCV_END(vbuffer); return str; } @@ -380,7 +380,7 @@ get_values(EVT_HANDLE handle) renderContext, handle, EvtRenderEventValues, 0, NULL, &bufferSize, &propCount); // bufferSize is in bytes, not array size - pRenderedValues = (PEVT_VARIANT)ALLOCV(vbuffer, bufferSize); + pRenderedValues = (PEVT_VARIANT)RB_ALLOCV(vbuffer, bufferSize); succeeded = EvtRender(renderContext, handle, @@ -391,14 +391,14 @@ get_values(EVT_HANDLE handle) &propCount); if (!succeeded) { DWORD status = GetLastError(); - ALLOCV_END(vbuffer); + RB_ALLOCV_END(vbuffer); EvtClose(renderContext); raise_system_error(rb_eWinevtQueryError, status); } userValues = extract_user_evt_variants(pRenderedValues, propCount); - ALLOCV_END(vbuffer); + RB_ALLOCV_END(vbuffer); EvtClose(renderContext); return userValues; @@ -596,8 +596,102 @@ get_description(EVT_HANDLE handle, LANGID langID, EVT_HANDLE hRemote) return _wcsdup(result.data()); } +static char* convert_wstr(wchar_t *wstr) +{ + VALUE vstr; + int len = 0; + CHAR *ptr = NULL; + DWORD err = ERROR_SUCCESS; + + len = WideCharToMultiByte(CP_UTF8, 0, wstr, -1, NULL, 0, NULL, NULL); + if (len == 0) { + return NULL; + } + + ptr = RB_ALLOCV_N(CHAR, vstr, len); + // For memory safety. + ZeroMemory(ptr, sizeof(CHAR) * len); + + len = WideCharToMultiByte(CP_UTF8, 0, wstr, -1, ptr, len, NULL, NULL); + // return 0 should be failure. + // ref: https://docs.microsoft.com/en-us/windows/win32/api/stringapiset/nf-stringapiset-widechartomultibyte#return-value + if (len == 0) { + err = GetLastError(); + RB_ALLOCV_END(vstr); + raise_system_error(rb_eRuntimeError, err); + } + + return strdup(ptr); +} + +static int ExpandSIDWString(PSID sid, CHAR **out_expanded) +{ +#define MAX_NAME 256 + DWORD len = MAX_NAME, err = ERROR_SUCCESS; + SID_NAME_USE sid_type = SidTypeUnknown; + WCHAR wAccount[MAX_NAME]; + WCHAR wDomain[MAX_NAME]; + CHAR *account = NULL, *domain = NULL; + DWORD result_len = 0; + CHAR *formatted = NULL; + VALUE vformatted; +#undef MAX_NAME + + if (!LookupAccountSidW(NULL, sid, + wAccount, &len, wDomain, + &len, &sid_type)) { + err = GetLastError(); + if (err == ERROR_NONE_MAPPED) { + goto none_mapped_error; + } + else { + return WINEVT_UTILS_ERROR_OTHERS; + } + + goto error; + } + + domain = convert_wstr(wDomain); + if (domain == NULL) { + goto error; + } + account = convert_wstr(wAccount); + if (account == NULL) { + goto error; + } + + result_len = strlen(domain) + 1 + strlen(account) + 1; + formatted = (CHAR *)RB_ALLOCV(vformatted, result_len); + if (formatted == NULL) { + goto error; + } + + _snprintf_s(formatted, result_len, _TRUNCATE, "%s\\%s", domain, account); + + *out_expanded = strdup(formatted); + + free(domain); + free(account); + RB_ALLOCV_END(vformatted); + + + return 0; + +none_mapped_error: + + return WINEVT_UTILS_ERROR_NONE_MAPPED; + +error: + err = GetLastError(); + + RB_ALLOCV_END(vformatted); + free(domain); + free(account); + raise_system_error(rb_eRuntimeError, err); +} + VALUE -render_system_event(EVT_HANDLE hEvent, BOOL preserve_qualifiers) +render_system_event(EVT_HANDLE hEvent, BOOL preserve_qualifiers, BOOL preserveSID_p) { DWORD status = ERROR_SUCCESS; EVT_HANDLE hContext = NULL; @@ -633,7 +727,7 @@ render_system_event(EVT_HANDLE hEvent, BOOL preserve_qualifiers) status = GetLastError(); if (ERROR_INSUFFICIENT_BUFFER == status) { dwBufferSize = dwBufferUsed; - pRenderedValues = (PEVT_VARIANT)ALLOCV(vRenderedValues, dwBufferSize); + pRenderedValues = (PEVT_VARIANT)RB_ALLOCV(vRenderedValues, dwBufferSize); if (pRenderedValues) { EvtRender(hContext, hEvent, @@ -651,7 +745,7 @@ render_system_event(EVT_HANDLE hEvent, BOOL preserve_qualifiers) if (ERROR_SUCCESS != status) { EvtClose(hContext); - ALLOCV_END(vRenderedValues); + RB_ALLOCV_END(vRenderedValues); rb_raise(rb_eWinevtQueryError, "EvtRender failed with %lu\n", status); } @@ -787,14 +881,23 @@ render_system_event(EVT_HANDLE hEvent, BOOL preserve_qualifiers) if (EvtVarTypeNull != pRenderedValues[EvtSystemUserID].Type) { if (ConvertSidToStringSid(pRenderedValues[EvtSystemUserID].SidVal, &pwsSid)) { - rbstr = rb_utf8_str_new_cstr(pwsSid); - rb_hash_aset(hash, rb_str_new2("UserID"), rbstr); - LocalFree(pwsSid); + CHAR *expandSID = NULL; + if (preserveSID_p) { + rbstr = rb_utf8_str_new_cstr(pwsSid); + rb_hash_aset(hash, rb_str_new2("UserID"), rbstr); + LocalFree(pwsSid); + } + if (ExpandSIDWString(pRenderedValues[EvtSystemUserID].SidVal, + &expandSID) == 0) { + rbstr = rb_utf8_str_new_cstr(expandSID); + free(expandSID); + rb_hash_aset(hash, rb_str_new2("User"), rbstr); + } } } EvtClose(hContext); - ALLOCV_END(vRenderedValues); + RB_ALLOCV_END(vRenderedValues); return hash; } diff --git a/ruby_install.ps1 b/ruby_install.ps1 new file mode 100644 index 0000000..2428390 --- /dev/null +++ b/ruby_install.ps1 @@ -0,0 +1,92 @@ +$rubies = @( + @{ + "version" = "Ruby 2.6.9-1" + "install_path" = "C:\Ruby26" + "download_url" = "https://github.com/oneclick/rubyinstaller2/releases/download/RubyInstaller-2.6.9-1/rubyinstaller-2.6.9-1-x86.exe" + "devkit_url" = "" + "devkit_paths" = @() + "bundlerV2" = $true + } + @{ + "version" = "Ruby 2.6.9-1 (x64)" + "install_path" = "C:\Ruby26-x64" + "download_url" = "https://github.com/oneclick/rubyinstaller2/releases/download/RubyInstaller-2.6.9-1/rubyinstaller-2.6.9-1-x64.exe" + "devkit_url" = "" + "devkit_paths" = @() + "bundlerV2" = $true + } + @{ + "version" = "Ruby 2.7.8-1" + "install_path" = "C:\Ruby27" + "download_url" = "https://github.com/oneclick/rubyinstaller2/releases/download/RubyInstaller-2.7.8-1/rubyinstaller-2.7.8-1-x86.exe" + "devkit_url" = "" + "devkit_paths" = @() + "bundlerV2" = $true + } + @{ + "version" = "Ruby 2.7.8-1 (x64)" + "install_path" = "C:\Ruby27-x64" + "download_url" = "https://github.com/oneclick/rubyinstaller2/releases/download/RubyInstaller-2.7.8-1/rubyinstaller-2.7.8-1-x64.exe" + "devkit_url" = "" + "devkit_paths" = @() + "bundlerV2" = $true + } +) + +function UpdateRubyPath($rubyPath) { + $env:path = ($env:path -split ';' | Where-Object { -not $_.contains('\Ruby') }) -join ';' + $env:path = "$rubyPath;$env:path" +} + +function Install-Ruby($ruby) { + Write-Host "Installing $($ruby.version)" -ForegroundColor Cyan + + # uninstall existing + $rubyUninstallPath = "$ruby.install_path\unins000.exe" + if ([IO.File]::Exists($rubyUninstallPath)) { + Write-Host " Uninstalling previous Ruby 2.4..." -ForegroundColor Gray + "`"$rubyUninstallPath`" /silent" | out-file "$env:temp\uninstall-ruby.cmd" -Encoding ASCII + & "$env:temp\uninstall-ruby.cmd" + del "$env:temp\uninstall-ruby.cmd" + Start-Sleep -s 5 + } + + if (Test-Path $ruby.install_path) { + Write-Host " Deleting $($ruby.install_path)" -ForegroundColor Gray + Remove-Item $ruby.install_path -Force -Recurse + } + + $exePath = "$($env:TEMP)\rubyinstaller.exe" + + Write-Host " Downloading $($ruby.version) from $($ruby.download_url)" -ForegroundColor Gray + (New-Object Net.WebClient).DownloadFile($ruby.download_url, $exePath) + + Write-Host "Installing..." -ForegroundColor Gray + cmd /c start /wait $exePath /verysilent /allusers /dir="$($ruby.install_path.replace('\', '/'))" /tasks="noassocfiles,nomodpath,noridkinstall" + del $exePath + Write-Host "Installed" -ForegroundColor Green + + # setup Ruby + UpdateRubyPath "$($ruby.install_path)\bin" + Write-Host "ruby --version" -ForegroundColor Gray + cmd /c ruby --version + + Write-Host "gem --version" -ForegroundColor Gray + cmd /c gem --version + + # list installed gems + Write-Host "gem list --local" -ForegroundColor Gray + cmd /c gem list --local + + # delete temp path + if ($tempPath) { + Write-Host " Cleaning up..." -ForegroundColor Gray + Remove-Item $tempPath -Force -Recurse + } + + Write-Host " Done!" -ForegroundColor Green +} + +for ($i = 0; $i -lt $rubies.Count; $i++) { + Install-Ruby $rubies[$i] +} \ No newline at end of file