diff --git a/Makefile b/Makefile index 7d5ac61..06598b0 100644 --- a/Makefile +++ b/Makefile @@ -51,7 +51,7 @@ TESTS := tests/addr.exe tests/addrcache.exe tests/ethernet.exe tools/fionread.ex # Tools to compile before running the test suite. TOOLS := tools/socket.exe tools/list-interfaces.exe tools/bind.exe tools/ipx-send.exe \ tools/ipx-recv.exe tools/spx-server.exe tools/spx-client.exe tools/ipx-isr.exe \ - tools/dptool.exe + tools/dptool.exe tools/osversion.exe tools/reg-set-bin.exe # DLLs to copy to the tools/ directory before running the test suite. TOOL_DLLS := tools/ipxwrapper.dll tools/wsock32.dll tools/mswsock.dll tools/dpwsockx.dll diff --git a/tests/lib/IPXWrapper/Tool/OSVersion.pm b/tests/lib/IPXWrapper/Tool/OSVersion.pm new file mode 100644 index 0000000..f74bd5b --- /dev/null +++ b/tests/lib/IPXWrapper/Tool/OSVersion.pm @@ -0,0 +1,103 @@ +# IPXWrapper test suite +# Copyright (C) 2024 Daniel Collins +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License version 2 as published by +# the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +# more details. +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., 51 +# Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +use strict; +use warnings; + +package IPXWrapper::Tool::OSVersion; + +use IPC::Open3; +use Test::Spec; + +my %os_version_cache = (); + +sub get +{ + my ($class, $host) = @_; + + if(defined $os_version_cache{$host}) + { + return $os_version_cache{$host}; + } + + my @command = ("ssh", $host, "Z:\\tools\\osversion.exe"); + + note(join(" ", @command)); + + # No need for error checking here - open3 throws on failure. + my $pid = open3(my $in, my $out, undef, @command); + + my $output = do { + local $/; + <$out>; + }; + + if($output =~ m/^(\d+).(\d+)\r?\n(\d+)\r?\n(\w+)\r?\n(.*)\r?\n$/) + { + my $self = bless({ + major => $1, + minor => $2, + build => $3, + platform => $4, + extra => $5, + }, $class); + + $os_version_cache{$host} = $self; + + return $self; + } + else{ + die("Didn't get expected output from osversion.exe:\n$output"); + } +} + +sub major +{ + my ($self) = @_; + return $self->{major}; +} + +sub minor +{ + my ($self) = @_; + return $self->{minor}; +} + +sub build +{ + my ($self) = @_; + return $self->{build}; +} + +sub platform +{ + my ($self) = @_; + return $self->{platform}; +} + +sub platform_is_win9x +{ + my ($self) = @_; + return $self->{platform} eq "VER_PLATFORM_WIN32_WINDOWS"; +} + +sub platform_is_winnt +{ + my ($self) = @_; + return $self->{platform} eq "VER_PLATFORM_WIN32_NT"; +} + +1; diff --git a/tests/lib/IPXWrapper/Util.pm b/tests/lib/IPXWrapper/Util.pm index 6224b8a..a87fc1e 100644 --- a/tests/lib/IPXWrapper/Util.pm +++ b/tests/lib/IPXWrapper/Util.pm @@ -1,5 +1,5 @@ # IPXWrapper test suite -# Copyright (C) 2014-2017 Daniel Collins +# Copyright (C) 2014-2024 Daniel Collins # # This program is free software; you can redistribute it and/or modify it # under the terms of the GNU General Public License version 2 as published by @@ -55,6 +55,8 @@ use Net::Libdnet::Eth; use NetPacket::IPX; use NetPacket::IPXWrapper; +use IPXWrapper::Tool::OSVersion; + sub run_remote_cmd { my ($host_ip, $exe_name, @exe_args) = @_; @@ -78,7 +80,21 @@ sub reg_set_dword { my ($host_ip, $key, $value, $data) = @_; - run_remote_cmd($host_ip, "REG", "ADD", $key, "/v", $value, "/t", "REG_DWORD", "/d", $data, "/f"); + if(IPXWrapper::Tool::OSVersion->get($host_ip)->platform_is_winnt()) + { + run_remote_cmd($host_ip, "REG", "ADD", $key, "/v", $value, "/t", "REG_DWORD", "/d", $data, "/f"); + } + else{ + eval { run_remote_cmd($host_ip, "REG", "QUERY", "$key\\$value"); }; + + if($@) + { + run_remote_cmd($host_ip, "REG", "ADD", "$key\\$value=$data", "REG_DWORD"); + } + else{ + run_remote_cmd($host_ip, "REG", "UPDATE", "$key\\$value=$data"); + } + } } sub reg_set_addr @@ -86,33 +102,71 @@ sub reg_set_addr my ($host_ip, $key, $value, $data) = @_; $data =~ s/://g; - run_remote_cmd($host_ip, "REG", "ADD", $key, "/v", $value, "/t", "REG_BINARY", "/d", $data, "/f"); + + if(IPXWrapper::Tool::OSVersion->get($host_ip)->platform_is_winnt()) + { + run_remote_cmd($host_ip, "REG", "ADD", $key, "/v", $value, "/t", "REG_BINARY", "/d", $data, "/f"); + } + else{ + run_remote_cmd($host_ip, "Z:\\tools\\reg-set-bin.exe", $key, $value, $data); + } } sub reg_set_string { my ($host_ip, $key, $value, $data) = @_; - run_remote_cmd($host_ip, "REG", "ADD", $key, "/v", $value, "/t", "REG_SZ", "/d", $data, "/f"); + if(IPXWrapper::Tool::OSVersion->get($host_ip)->platform_is_winnt()) + { + run_remote_cmd($host_ip, "REG", "ADD", $key, "/v", $value, "/t", "REG_SZ", "/d", $data, "/f"); + } + else{ + eval { run_remote_cmd($host_ip, "REG", "QUERY", "$key\\$value"); }; + + if($@) + { + run_remote_cmd($host_ip, "REG", "ADD", "$key\\$value=$data", "REG_SZ"); + } + else{ + run_remote_cmd($host_ip, "REG", "UPDATE", "$key\\$value=$data"); + } + } } sub reg_delete_key { my ($host_ip, $key) = @_; - # Attempting to delete a key which doesn't exist is considered to be an - # error, so we touch the key beforehand. + eval { run_remote_cmd($host_ip, "REG", "QUERY", $key); }; - run_remote_cmd($host_ip, "REG", "ADD", $key, "/f"); - run_remote_cmd($host_ip, "REG", "DELETE", $key, "/f"); + unless($@) + { + if(IPXWrapper::Tool::OSVersion->get($host_ip)->platform_is_winnt()) + { + run_remote_cmd($host_ip, "REG", "DELETE", $key, "/f"); + } + else{ + run_remote_cmd($host_ip, "REG", "DELETE", $key, "/FORCE"); + } + } } sub reg_delete_value { my ($host_ip, $key, $value) = @_; - run_remote_cmd($host_ip, "REG", "ADD", $key, "/v", $value, "/t", "REG_SZ", "/f"); - run_remote_cmd($host_ip, "REG", "DELETE", $key, "/v", $value, "/f"); + eval { run_remote_cmd($host_ip, "REG", "QUERY", "$key\\$value"); }; + + unless($@) + { + if(IPXWrapper::Tool::OSVersion->get($host_ip)->platform_is_winnt()) + { + run_remote_cmd($host_ip, "REG", "DELETE", $key, "/v", $value, "/f"); + } + else{ + run_remote_cmd($host_ip, "REG", "DELETE", "$key\\$value", "/FORCE"); + } + } } sub send_ipx_over_udp diff --git a/tools/osversion.c b/tools/osversion.c new file mode 100644 index 0000000..c382073 --- /dev/null +++ b/tools/osversion.c @@ -0,0 +1,61 @@ +/* IPXWrapper test tools + * Copyright (C) 2024 Daniel Collins + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 51 + * Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#include +#include + +int main(int argc, char **argv) +{ + OSVERSIONINFO osver; + osver.dwOSVersionInfoSize = sizeof(osver); + + if(!GetVersionEx(&osver)) + { + fprintf(stderr, "GetVersionEx: %u\n", (unsigned)(GetLastError())); + return 1; + } + + const char *platform; + + switch(osver.dwPlatformId) + { + case VER_PLATFORM_WIN32s: + platform = "VER_PLATFORM_WIN32s"; + break; + + case VER_PLATFORM_WIN32_WINDOWS: + platform = "VER_PLATFORM_WIN32_WINDOWS"; + break; + + case VER_PLATFORM_WIN32_NT: + platform = "VER_PLATFORM_WIN32_NT"; + break; + + default: + platform = "VER_PLATFORM_UNKNOWN"; + break; + } + + printf("%u.%u\n%u\n%s\n%s\n", + (unsigned)(osver.dwMajorVersion), + (unsigned)(osver.dwMinorVersion), + (unsigned)(osver.dwBuildNumber), + platform, + osver.szCSDVersion); + + return 0; +} diff --git a/tools/reg-set-bin.c b/tools/reg-set-bin.c new file mode 100644 index 0000000..9e6e1f3 --- /dev/null +++ b/tools/reg-set-bin.c @@ -0,0 +1,140 @@ +/* IPXWrapper test tools + * Copyright (C) 2024 Daniel Collins + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 51 + * Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +/* "Why do you have a standalone program specifically for storing binary + * registry values rather than using REG.EXE like you do for everything else?" + * + * "I'm so glad you asked that, hypothetical voice in my head, BEHOLD." + * + * > REG ADD HKCU\Software\IPXWrapper\08:00:27:C3:6A:E6\net=00000001 REG_BINARY + * > Adding Binary Format Data is not Supported. + * > The operation completed successfully. +*/ + +#include +#include +#include +#include +#include + +static unsigned char hex_nibble_to_bin(char hex) +{ + if(hex >= '0' && hex <= '9') + { + return 0x0 + (hex - '0'); + } + else if(hex >= 'A' && hex <= 'F') + { + return 0xA + (hex - 'A'); + } + else if(hex >= 'a' && hex <= 'f') + { + return 0xA + (hex - 'a'); + } + else{ + abort(); + } +} + +int main(int argc, char **argv) +{ + if(argc != 4) + { + fprintf(stderr, "Usage: %s \n", argv[0]); + return 1; + } + + const char *key_path = argv[1]; + const char *value_name = argv[2]; + const char *value_hex = argv[3]; + + HKEY root_key; + + if(strncmp(key_path, "HKLM", 4) == 0 && (key_path[4] == '\\' || key_path[4] == '\0')) + { + root_key = HKEY_LOCAL_MACHINE; + key_path += 4; + } + else if(strncmp(key_path, "HKCU", 4) == 0 && (key_path[4] == '\\' || key_path[4] == '\0')) + { + root_key = HKEY_CURRENT_USER; + key_path += 4; + } + else{ + fprintf(stderr, "Key path must begin with HKLM or HKCU\n"); + return 1; + } + + if(key_path[0] == '\\') + { + ++key_path; + } + + size_t hex_len = strlen(value_hex); + + if((hex_len % 2) != 0) + { + fprintf(stderr, "Invalid byte string\n"); + return 1; + } + + unsigned char *data = malloc(hex_len / 2); + if(hex_len > 0 && data == NULL) + { + fprintf(stderr, "Error allocating memory\n"); + return 1; + } + + size_t data_len = hex_len / 2; + + for(size_t i = 0; i < hex_len; i += 2) + { + char hex1 = value_hex[i]; + char hex2 = value_hex[i + 1]; + + if(isxdigit(hex1) && isxdigit(hex2)) + { + data[i / 2] = (hex_nibble_to_bin(hex1) << 4) | hex_nibble_to_bin(hex2); + } + else{ + fprintf(stderr, "Invalid byte string\n"); + return 1; + } + } + + HKEY key; + DWORD error = RegCreateKeyEx(root_key, key_path, 0, NULL, 0, KEY_READ | KEY_WRITE, NULL, &key, NULL); + if(error != ERROR_SUCCESS) + { + fprintf(stderr, "Error opening/creating registry key (error code %u)\n", (unsigned)(error)); + return 1; + } + + error = RegSetValueEx(key, value_name, 0, REG_BINARY, (BYTE*)(data), data_len); + + RegCloseKey(key); + free(data); + + if(error == ERROR_SUCCESS) + { + return 0; + } + else{ + fprintf(stderr, "Error code %u when writing registry value\n", (unsigned)(error)); + return 1; + } +}