Update pre0.77
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
#define RELEASE 0.76
|
||||
#define TEXTVER "Release 0.76"
|
||||
#define SSHVER "-Release-0.76"
|
||||
#define BINARY_VERSION 0,76,1,4
|
||||
#define BINARY_VERSION 0,76,1,5
|
||||
#define SOURCE_COMMIT "unavailable"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -130,6 +130,7 @@ static tree234 *sktree;
|
||||
int get_param( const char * val ) ;
|
||||
#endif
|
||||
#ifdef MOD_ZMODEM
|
||||
int GetPuttyFlag(void) ;
|
||||
static int curpass;
|
||||
#endif
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
#define APPDESC "PuTTY SSH authentication agent"
|
||||
#endif
|
||||
|
||||
#include "winhelp.rc2"
|
||||
#include "help.rc2"
|
||||
|
||||
#ifdef MOD_INTEGRATED_AGENT
|
||||
900 ICON "pageant.ico"
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#define APPNAME "PuTTY"
|
||||
#define APPDESC "SSH, Telnet, Rlogin, and SUPDUP client"
|
||||
|
||||
#include "winhelp.rc2"
|
||||
#include "help.rc2"
|
||||
|
||||
IDI_MAINICON ICON "putty.ico"
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
#define APPNAME_KEYGEN "PuTTYgen"
|
||||
#define APPDESC_KEYGEN "PuTTY SSH key generation utility"
|
||||
|
||||
#include "winhelp.rc2"
|
||||
#include "help.rc2"
|
||||
#include "puttygen-rc.h"
|
||||
|
||||
800 ICON "puttygen.ico"
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#define APPNAME "PuTTYtel"
|
||||
#define APPDESC "Telnet and Rlogin client"
|
||||
|
||||
#include "winhelp.rc2"
|
||||
#include "help.rc2"
|
||||
#include "putty-common.rc2"
|
||||
|
||||
#ifndef NO_MANIFESTS
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* If the OS provides it, call SetDefaultDllDirectories() to prevent
|
||||
* DLLs from being loaded from the directory containing our own
|
||||
* binary, and instead only load from system32.
|
||||
*
|
||||
* This is a protection against hijacking attacks, if someone runs
|
||||
* PuTTY directly from their web browser's download directory having
|
||||
* previously been enticed into clicking on an unwise link that
|
||||
* downloaded a malicious DLL to the same directory under one of
|
||||
* various magic names that seem to be things that standard Windows
|
||||
* DLLs delegate to.
|
||||
*
|
||||
* It shouldn't break deliberate loading of user-provided DLLs such as
|
||||
* GSSAPI providers, because those are specified by their full
|
||||
* pathname by the user-provided configuration.
|
||||
*/
|
||||
|
||||
#include "putty.h"
|
||||
|
||||
void dll_hijacking_protection(void)
|
||||
{
|
||||
static HMODULE kernel32_module;
|
||||
DECL_WINDOWS_FUNCTION(static, BOOL, SetDefaultDllDirectories, (DWORD));
|
||||
|
||||
if (!kernel32_module) {
|
||||
kernel32_module = load_system32_dll("kernel32.dll");
|
||||
#if !HAVE_SETDEFAULTDLLDIRECTORIES
|
||||
/* For older Visual Studio, this function isn't available in
|
||||
* the header files to type-check */
|
||||
GET_WINDOWS_FUNCTION_NO_TYPECHECK(
|
||||
kernel32_module, SetDefaultDllDirectories);
|
||||
#else
|
||||
GET_WINDOWS_FUNCTION(kernel32_module, SetDefaultDllDirectories);
|
||||
#endif
|
||||
}
|
||||
|
||||
if (p_SetDefaultDllDirectories) {
|
||||
/* LOAD_LIBRARY_SEARCH_SYSTEM32 and explicitly specified
|
||||
* directories only */
|
||||
p_SetDefaultDllDirectories(LOAD_LIBRARY_SEARCH_SYSTEM32 |
|
||||
LOAD_LIBRARY_SEARCH_USER_DIRS);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Implementation of dputs() for Windows.
|
||||
*
|
||||
* The debug messages are written to STD_OUTPUT_HANDLE, except that
|
||||
* first it has to make sure that handle _exists_, by calling
|
||||
* AllocConsole first if necessary.
|
||||
*
|
||||
* They also go into a file called debug.log.
|
||||
*/
|
||||
|
||||
#include "putty.h"
|
||||
#include "utils/utils.h"
|
||||
|
||||
static HANDLE debug_fp = INVALID_HANDLE_VALUE;
|
||||
static HANDLE debug_hdl = INVALID_HANDLE_VALUE;
|
||||
static int debug_got_console = 0;
|
||||
|
||||
void dputs(const char *buf)
|
||||
{
|
||||
DWORD dw;
|
||||
|
||||
if (!debug_got_console) {
|
||||
if (AllocConsole()) {
|
||||
debug_got_console = 1;
|
||||
debug_hdl = GetStdHandle(STD_OUTPUT_HANDLE);
|
||||
}
|
||||
}
|
||||
if (debug_fp == INVALID_HANDLE_VALUE) {
|
||||
debug_fp = CreateFile("debug.log", GENERIC_WRITE, FILE_SHARE_READ,
|
||||
NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
|
||||
}
|
||||
|
||||
if (debug_fp != INVALID_HANDLE_VALUE) {
|
||||
WriteFile(debug_fp, buf, strlen(buf), &dw, NULL);
|
||||
}
|
||||
if (debug_hdl != INVALID_HANDLE_VALUE) {
|
||||
WriteFile(debug_hdl, buf, strlen(buf), &dw, NULL);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Escaping/unescaping functions to translate between a saved session
|
||||
* name, and the key name used to represent it in the Registry area
|
||||
* where we store saved sessions.
|
||||
*
|
||||
* The basic technique is to %-escape characters we can't use in
|
||||
* Registry keys.
|
||||
*/
|
||||
|
||||
#include "putty.h"
|
||||
|
||||
void escape_registry_key(const char *in, strbuf *out)
|
||||
{
|
||||
bool candot = false;
|
||||
static const char hex[16] = "0123456789ABCDEF";
|
||||
|
||||
while (*in) {
|
||||
if (*in == ' ' || *in == '\\' || *in == '*' || *in == '?' ||
|
||||
*in == '%' || *in < ' ' || *in > '~' || (*in == '.'
|
||||
&& !candot)) {
|
||||
put_byte(out, '%');
|
||||
put_byte(out, hex[((unsigned char) *in) >> 4]);
|
||||
put_byte(out, hex[((unsigned char) *in) & 15]);
|
||||
} else
|
||||
put_byte(out, *in);
|
||||
in++;
|
||||
candot = true;
|
||||
}
|
||||
}
|
||||
|
||||
void unescape_registry_key(const char *in, strbuf *out)
|
||||
{
|
||||
while (*in) {
|
||||
if (*in == '%' && in[1] && in[2]) {
|
||||
int i, j;
|
||||
|
||||
i = in[1] - '0';
|
||||
i -= (i > 9 ? 7 : 0);
|
||||
j = in[2] - '0';
|
||||
j -= (j > 9 ? 7 : 0);
|
||||
|
||||
put_byte(out, (i << 4) + j);
|
||||
in += 3;
|
||||
} else {
|
||||
put_byte(out, *in++);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Implementation of Filename for Windows.
|
||||
*/
|
||||
|
||||
#include "putty.h"
|
||||
|
||||
Filename *filename_from_str(const char *str)
|
||||
{
|
||||
Filename *ret = snew(Filename);
|
||||
ret->path = dupstr(str);
|
||||
return ret;
|
||||
}
|
||||
|
||||
Filename *filename_copy(const Filename *fn)
|
||||
{
|
||||
return filename_from_str(fn->path);
|
||||
}
|
||||
|
||||
const char *filename_to_str(const Filename *fn)
|
||||
{
|
||||
return fn->path;
|
||||
}
|
||||
|
||||
bool filename_equal(const Filename *f1, const Filename *f2)
|
||||
{
|
||||
return !strcmp(f1->path, f2->path);
|
||||
}
|
||||
|
||||
bool filename_is_null(const Filename *fn)
|
||||
{
|
||||
return !*fn->path;
|
||||
}
|
||||
|
||||
void filename_free(Filename *fn)
|
||||
{
|
||||
sfree(fn->path);
|
||||
sfree(fn);
|
||||
}
|
||||
|
||||
void filename_serialise(BinarySink *bs, const Filename *f)
|
||||
{
|
||||
put_asciz(bs, f->path);
|
||||
}
|
||||
Filename *filename_deserialise(BinarySource *src)
|
||||
{
|
||||
return filename_from_str(get_asciz(src));
|
||||
}
|
||||
|
||||
char filename_char_sanitise(char c)
|
||||
{
|
||||
if (strchr("<>:\"/\\|?*", c))
|
||||
return '.';
|
||||
return c;
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Implementation of FontSpec for Windows.
|
||||
*/
|
||||
|
||||
#include "putty.h"
|
||||
|
||||
FontSpec *fontspec_new(const char *name, bool bold, int height, int charset)
|
||||
{
|
||||
FontSpec *f = snew(FontSpec);
|
||||
f->name = dupstr(name);
|
||||
f->isbold = bold;
|
||||
f->height = height;
|
||||
f->charset = charset;
|
||||
return f;
|
||||
}
|
||||
FontSpec *fontspec_copy(const FontSpec *f)
|
||||
{
|
||||
return fontspec_new(f->name, f->isbold, f->height, f->charset);
|
||||
}
|
||||
void fontspec_free(FontSpec *f)
|
||||
{
|
||||
sfree(f->name);
|
||||
sfree(f);
|
||||
}
|
||||
void fontspec_serialise(BinarySink *bs, FontSpec *f)
|
||||
{
|
||||
put_asciz(bs, f->name);
|
||||
put_uint32(bs, f->isbold);
|
||||
put_uint32(bs, f->height);
|
||||
put_uint32(bs, f->charset);
|
||||
}
|
||||
FontSpec *fontspec_deserialise(BinarySource *src)
|
||||
{
|
||||
const char *name = get_asciz(src);
|
||||
unsigned isbold = get_uint32(src);
|
||||
unsigned height = get_uint32(src);
|
||||
unsigned charset = get_uint32(src);
|
||||
return fontspec_new(name, isbold, height, charset);
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Implementation of get_username() for Windows.
|
||||
*/
|
||||
|
||||
#include "putty.h"
|
||||
|
||||
#ifndef SECURITY_WIN32
|
||||
#define SECURITY_WIN32
|
||||
#endif
|
||||
#include <security.h>
|
||||
|
||||
char *get_username(void)
|
||||
{
|
||||
DWORD namelen;
|
||||
char *user;
|
||||
bool got_username = false;
|
||||
DECL_WINDOWS_FUNCTION(static, BOOLEAN, GetUserNameExA,
|
||||
(EXTENDED_NAME_FORMAT, LPSTR, PULONG));
|
||||
|
||||
{
|
||||
static bool tried_usernameex = false;
|
||||
if (!tried_usernameex) {
|
||||
/* Not available on Win9x, so load dynamically */
|
||||
HMODULE secur32 = load_system32_dll("secur32.dll");
|
||||
/* If MIT Kerberos is installed, the following call to
|
||||
GET_WINDOWS_FUNCTION makes Windows implicitly load
|
||||
sspicli.dll WITHOUT proper path sanitizing, so better
|
||||
load it properly before */
|
||||
HMODULE sspicli = load_system32_dll("sspicli.dll");
|
||||
(void)sspicli; /* squash compiler warning about unused variable */
|
||||
GET_WINDOWS_FUNCTION(secur32, GetUserNameExA);
|
||||
tried_usernameex = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (p_GetUserNameExA) {
|
||||
/*
|
||||
* If available, use the principal -- this avoids the problem
|
||||
* that the local username is case-insensitive but Kerberos
|
||||
* usernames are case-sensitive.
|
||||
*/
|
||||
|
||||
/* Get the length */
|
||||
namelen = 0;
|
||||
(void) p_GetUserNameExA(NameUserPrincipal, NULL, &namelen);
|
||||
|
||||
user = snewn(namelen, char);
|
||||
got_username = p_GetUserNameExA(NameUserPrincipal, user, &namelen);
|
||||
if (got_username) {
|
||||
char *p = strchr(user, '@');
|
||||
if (p) *p = 0;
|
||||
} else {
|
||||
sfree(user);
|
||||
}
|
||||
}
|
||||
|
||||
if (!got_username) {
|
||||
/* Fall back to local user name */
|
||||
namelen = 0;
|
||||
if (!GetUserName(NULL, &namelen)) {
|
||||
/*
|
||||
* Apparently this doesn't work at least on Windows XP SP2.
|
||||
* Thus assume a maximum of 256. It will fail again if it
|
||||
* doesn't fit.
|
||||
*/
|
||||
namelen = 256;
|
||||
}
|
||||
|
||||
user = snewn(namelen, char);
|
||||
got_username = GetUserName(user, &namelen);
|
||||
if (!got_username) {
|
||||
sfree(user);
|
||||
}
|
||||
}
|
||||
|
||||
return got_username ? user : NULL;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
/*
|
||||
* Determine whether a Windows HANDLE points at a console device.
|
||||
*/
|
||||
|
||||
#include "putty.h"
|
||||
|
||||
bool is_console_handle(HANDLE handle)
|
||||
{
|
||||
DWORD ignored_output;
|
||||
if (GetConsoleMode(handle, &ignored_output))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Wrapper function to load a DLL out of c:\windows\system32 without
|
||||
* going through the full DLL search path. (Hence no attack is
|
||||
* possible by placing a substitute DLL earlier on that path.)
|
||||
*/
|
||||
|
||||
#include "putty.h"
|
||||
|
||||
HMODULE load_system32_dll(const char *libname)
|
||||
{
|
||||
/*
|
||||
* Wrapper function to load a DLL out of c:\windows\system32
|
||||
* without going through the full DLL search path. (Hence no
|
||||
* attack is possible by placing a substitute DLL earlier on that
|
||||
* path.)
|
||||
*/
|
||||
static char *sysdir = NULL;
|
||||
static size_t sysdirsize = 0;
|
||||
char *fullpath;
|
||||
HMODULE ret;
|
||||
|
||||
if (!sysdir) {
|
||||
size_t len;
|
||||
while ((len = GetSystemDirectory(sysdir, sysdirsize)) >= sysdirsize)
|
||||
sgrowarray(sysdir, sysdirsize, len);
|
||||
}
|
||||
|
||||
fullpath = dupcat(sysdir, "\\", libname);
|
||||
ret = LoadLibrary(fullpath);
|
||||
sfree(fullpath);
|
||||
return ret;
|
||||
}
|
||||
@@ -1,26 +1,17 @@
|
||||
/*
|
||||
* winmiscs.c: Windows-specific standalone functions. Has the same
|
||||
* relationship to winmisc.c that utils.c does to misc.c, but the
|
||||
* corresponding name 'winutils.c' was already taken.
|
||||
* 'Minefield' - a crude Windows memory debugger, similar in concept
|
||||
* to the old Unix 'Electric Fence'. The main difference is that
|
||||
* Electric Fence can be imposed on a program from outside, via
|
||||
* LD_PRELOAD, whereas this has to be included in the program at
|
||||
* compile time with its own cooperation.
|
||||
*
|
||||
* This module provides the Minefield allocator. Actually enabling it
|
||||
* is done by a #define in force when the main utils/memory.c is
|
||||
* compiled.
|
||||
*/
|
||||
|
||||
#include "putty.h"
|
||||
|
||||
#ifndef NO_SECUREZEROMEMORY
|
||||
/*
|
||||
* Windows implementation of smemclr (see misc.c) using SecureZeroMemory.
|
||||
*/
|
||||
void smemclr(void *b, size_t n) {
|
||||
if (b && n > 0)
|
||||
SecureZeroMemory(b, n);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef MINEFIELD
|
||||
/*
|
||||
* Minefield - a Windows equivalent for Electric Fence
|
||||
*/
|
||||
|
||||
#define PAGESIZE 4096
|
||||
|
||||
/*
|
||||
@@ -234,52 +225,3 @@ void *minefield_c_realloc(void *p, size_t size)
|
||||
minefield_free(p);
|
||||
return q;
|
||||
}
|
||||
|
||||
#endif /* MINEFIELD */
|
||||
|
||||
#if defined _MSC_VER && _MSC_VER < 1800
|
||||
|
||||
/*
|
||||
* Work around lack of strtoumax in older MSVC libraries
|
||||
*/
|
||||
uintmax_t strtoumax(const char *nptr, char **endptr, int base)
|
||||
{
|
||||
return _strtoui64(nptr, endptr, base);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#if defined _M_ARM || defined _M_ARM64
|
||||
|
||||
bool platform_aes_hw_available(void)
|
||||
{
|
||||
return IsProcessorFeaturePresent(PF_ARM_V8_CRYPTO_INSTRUCTIONS_AVAILABLE);
|
||||
}
|
||||
|
||||
bool platform_sha256_hw_available(void)
|
||||
{
|
||||
return IsProcessorFeaturePresent(PF_ARM_V8_CRYPTO_INSTRUCTIONS_AVAILABLE);
|
||||
}
|
||||
|
||||
bool platform_sha1_hw_available(void)
|
||||
{
|
||||
return IsProcessorFeaturePresent(PF_ARM_V8_CRYPTO_INSTRUCTIONS_AVAILABLE);
|
||||
}
|
||||
|
||||
bool platform_sha512_hw_available(void)
|
||||
{
|
||||
/* As of 2020-12-24, as far as I can tell from docs.microsoft.com,
|
||||
* Windows on Arm does not yet provide a PF_ARM_V8_* flag for the
|
||||
* SHA-512 architecture extension. */
|
||||
return false;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
bool is_console_handle(HANDLE handle)
|
||||
{
|
||||
DWORD ignored_output;
|
||||
if (GetConsoleMode(handle, &ignored_output))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Implementation of open_for_write_would_lose_data for Windows.
|
||||
*/
|
||||
|
||||
#include "putty.h"
|
||||
|
||||
/*
|
||||
* This is slightly fiddly because we want to be backwards-compatible
|
||||
* with systems too old to have GetFileAttributesEx. The next best
|
||||
* thing is FindFirstFile, which will return a different data
|
||||
* structure, but one that also contains the fields we want. (But it
|
||||
* will behave more unhelpfully - for this application - in the
|
||||
* presence of wildcards, so we'd prefer to use GFAE if we can.)
|
||||
*/
|
||||
|
||||
bool open_for_write_would_lose_data(const Filename *fn)
|
||||
{
|
||||
WIN32_FILE_ATTRIBUTE_DATA attrs;
|
||||
if (!GetFileAttributesEx(fn->path, GetFileExInfoStandard, &attrs)) {
|
||||
/*
|
||||
* Generally, if we don't identify a specific reason why we
|
||||
* should return true from this function, we return false, and
|
||||
* let the subsequent attempt to open the file for real give a
|
||||
* more useful error message.
|
||||
*/
|
||||
return false;
|
||||
}
|
||||
if (attrs.dwFileAttributes & (FILE_ATTRIBUTE_DEVICE |
|
||||
FILE_ATTRIBUTE_DIRECTORY)) {
|
||||
/*
|
||||
* File is something other than an ordinary disk file, so
|
||||
* opening it for writing will not cause truncation. (It may
|
||||
* not _succeed_ either, but that's not our problem here!)
|
||||
*/
|
||||
return false;
|
||||
}
|
||||
if (attrs.nFileSizeHigh == 0 && attrs.nFileSizeLow == 0) {
|
||||
/*
|
||||
* File is zero-length (or may be a named pipe, which
|
||||
* dwFileAttributes can't tell apart from a regular file), so
|
||||
* opening it for writing won't truncate any data away because
|
||||
* there's nothing to truncate anyway.
|
||||
*/
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
/*
|
||||
* Implementation of platform_get_x_display for Windows, common to all
|
||||
* tools.
|
||||
*/
|
||||
|
||||
#include "putty.h"
|
||||
|
||||
char *platform_get_x_display(void)
|
||||
{
|
||||
/* We may as well check for DISPLAY in case it's useful. */
|
||||
return dupstr(getenv("DISPLAY"));
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Self-contained function to try to fetch a single string value from
|
||||
* the Registry, and return it as a dynamically allocated C string.
|
||||
*/
|
||||
|
||||
#include "putty.h"
|
||||
|
||||
char *registry_get_string(HKEY root, const char *path, const char *leaf)
|
||||
{
|
||||
HKEY key = root;
|
||||
bool need_close_key = false;
|
||||
char *toret = NULL, *str = NULL;
|
||||
|
||||
if (path) {
|
||||
if (RegCreateKey(key, path, &key) != ERROR_SUCCESS)
|
||||
goto out;
|
||||
need_close_key = true;
|
||||
}
|
||||
|
||||
DWORD type, size;
|
||||
if (RegQueryValueEx(key, leaf, 0, &type, NULL, &size) != ERROR_SUCCESS)
|
||||
goto out;
|
||||
if (type != REG_SZ)
|
||||
goto out;
|
||||
|
||||
str = snewn(size + 1, char);
|
||||
DWORD size_got = size;
|
||||
if (RegQueryValueEx(key, leaf, 0, &type, (LPBYTE)str,
|
||||
&size_got) != ERROR_SUCCESS)
|
||||
goto out;
|
||||
if (type != REG_SZ || size_got > size)
|
||||
goto out;
|
||||
str[size_got] = '\0';
|
||||
|
||||
toret = str;
|
||||
str = NULL;
|
||||
|
||||
out:
|
||||
if (need_close_key)
|
||||
RegCloseKey(key);
|
||||
sfree(str);
|
||||
return toret;
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Securely wipe memory.
|
||||
*
|
||||
* The actual wiping is no different from what memset would do: the
|
||||
* point of 'securely' is to try to be sure over-clever compilers
|
||||
* won't optimise away memsets on variables that are about to be freed
|
||||
* or go out of scope. See
|
||||
* https://buildsecurityin.us-cert.gov/bsi-rules/home/g1/771-BSI.html
|
||||
*/
|
||||
|
||||
#include "defs.h"
|
||||
#include "misc.h"
|
||||
|
||||
/*
|
||||
* Trivial function that is given a pointer to some memory and ignores
|
||||
* it.
|
||||
*/
|
||||
static void no_op(void *ptr, size_t size) {}
|
||||
|
||||
/*
|
||||
* Function pointer that is given a pointer to some memory, and from
|
||||
* the compiler's point of view, _might_ read it, or otherwise depend
|
||||
* on its contents.
|
||||
*
|
||||
* In fact, this function pointer always points to no_op() above. But
|
||||
* because the pointer itself is volatile-qualified, the compiler
|
||||
* isn't allowed to optimise based on the assumption that that will
|
||||
* always be the case. So it has to call through the function pointer
|
||||
* anyway, on the basis that it _might_ have magically changed at run
|
||||
* time into a pointer to some completely arbitrary function. And
|
||||
* therefore it must also avoid optimising away any observable effect
|
||||
* beforehand that a completely arbitrary function might depend on -
|
||||
* such as the zeroing of our memory region.
|
||||
*/
|
||||
static void (*const volatile maybe_read)(void *ptr, size_t size) = no_op;
|
||||
|
||||
void smemclr(void *b, size_t n)
|
||||
{
|
||||
if (b && n > 0) {
|
||||
/*
|
||||
* Zero out the memory.
|
||||
*/
|
||||
memset(b, 0, n);
|
||||
|
||||
/*
|
||||
* Call the above function pointer, which (for all the
|
||||
* compiler knows) might check that we've really zeroed the
|
||||
* memory.
|
||||
*/
|
||||
maybe_read(b, n);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
/*
|
||||
* Work around lack of strtoumax in older MSVC libraries.
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "defs.h"
|
||||
|
||||
uintmax_t strtoumax(const char *nptr, char **endptr, int base)
|
||||
{
|
||||
return _strtoui64(nptr, endptr, base);
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
#include "putty.h"
|
||||
|
||||
DWORD osMajorVersion, osMinorVersion, osPlatformId;
|
||||
|
||||
void init_winver(void)
|
||||
{
|
||||
static bool initialised = false;
|
||||
if (initialised)
|
||||
return;
|
||||
initialised = true;
|
||||
|
||||
OSVERSIONINFO osVersion;
|
||||
static HMODULE kernel32_module;
|
||||
DECL_WINDOWS_FUNCTION(static, BOOL, GetVersionExA, (LPOSVERSIONINFO));
|
||||
|
||||
if (!kernel32_module) {
|
||||
kernel32_module = load_system32_dll("kernel32.dll");
|
||||
/* Deliberately don't type-check this function, because that
|
||||
* would involve using its declaration in a header file which
|
||||
* triggers a deprecation warning. I know it's deprecated (see
|
||||
* below) and don't need telling. */
|
||||
GET_WINDOWS_FUNCTION_NO_TYPECHECK(kernel32_module, GetVersionExA);
|
||||
}
|
||||
|
||||
ZeroMemory(&osVersion, sizeof(osVersion));
|
||||
osVersion.dwOSVersionInfoSize = sizeof (OSVERSIONINFO);
|
||||
if (p_GetVersionExA && p_GetVersionExA(&osVersion)) {
|
||||
osMajorVersion = osVersion.dwMajorVersion;
|
||||
osMinorVersion = osVersion.dwMinorVersion;
|
||||
osPlatformId = osVersion.dwPlatformId;
|
||||
} else {
|
||||
/*
|
||||
* GetVersionEx is deprecated, so allow for it perhaps going
|
||||
* away in future API versions. If it's not there, simply
|
||||
* assume that's because Windows is too _new_, so fill in the
|
||||
* variables we care about to a value that will always compare
|
||||
* higher than any given test threshold.
|
||||
*
|
||||
* Normally we should be checking against the presence of a
|
||||
* specific function if possible in any case.
|
||||
*/
|
||||
osMajorVersion = osMinorVersion = UINT_MAX; /* a very high number */
|
||||
osPlatformId = VER_PLATFORM_WIN32_NT; /* not Win32s or Win95-like */
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Wrapper around the Windows FormatMessage system for retrieving the
|
||||
* text of a system error code, with a simple API similar to strerror.
|
||||
*
|
||||
* Works by keeping a tree234 containing mappings from system error
|
||||
* codes to strings. Entries allocated in this tree are simply never
|
||||
* freed.
|
||||
*
|
||||
* Also, the returned string has its trailing newline removed (so it
|
||||
* can go in places like the Event Log that never want a newline), and
|
||||
* is prefixed with the error number (so that if a user sends an error
|
||||
* report containing a translated error message we can't read, we can
|
||||
* still find out what the error actually was).
|
||||
*/
|
||||
|
||||
#include "putty.h"
|
||||
|
||||
struct errstring {
|
||||
int error;
|
||||
char *text;
|
||||
};
|
||||
|
||||
static int errstring_find(void *av, void *bv)
|
||||
{
|
||||
int *a = (int *)av;
|
||||
struct errstring *b = (struct errstring *)bv;
|
||||
if (*a < b->error)
|
||||
return -1;
|
||||
if (*a > b->error)
|
||||
return +1;
|
||||
return 0;
|
||||
}
|
||||
static int errstring_compare(void *av, void *bv)
|
||||
{
|
||||
struct errstring *a = (struct errstring *)av;
|
||||
return errstring_find(&a->error, bv);
|
||||
}
|
||||
|
||||
static tree234 *errstrings = NULL;
|
||||
|
||||
const char *win_strerror(int error)
|
||||
{
|
||||
struct errstring *es;
|
||||
|
||||
if (!errstrings)
|
||||
errstrings = newtree234(errstring_compare);
|
||||
|
||||
es = find234(errstrings, &error, errstring_find);
|
||||
|
||||
if (!es) {
|
||||
char msgtext[65536]; /* maximum size for FormatMessage is 64K */
|
||||
|
||||
es = snew(struct errstring);
|
||||
es->error = error;
|
||||
if (!FormatMessage((FORMAT_MESSAGE_FROM_SYSTEM |
|
||||
FORMAT_MESSAGE_IGNORE_INSERTS), NULL, error,
|
||||
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
|
||||
msgtext, lenof(msgtext)-1, NULL)) {
|
||||
sprintf(msgtext,
|
||||
"(unable to format: FormatMessage returned %u)",
|
||||
(unsigned int)GetLastError());
|
||||
} else {
|
||||
int len = strlen(msgtext);
|
||||
if (len > 0 && msgtext[len-1] == '\n')
|
||||
msgtext[len-1] = '\0';
|
||||
}
|
||||
es->text = dupprintf("Error %d: %s", error, msgtext);
|
||||
add234(errstrings, es);
|
||||
}
|
||||
|
||||
return es->text;
|
||||
}
|
||||
@@ -1 +1 @@
|
||||
4
|
||||
5
|
||||
|
||||
@@ -1,467 +0,0 @@
|
||||
/*
|
||||
* winmisc.c: miscellaneous Windows-specific things
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <limits.h>
|
||||
#include "putty.h"
|
||||
#ifndef SECURITY_WIN32
|
||||
#define SECURITY_WIN32
|
||||
#endif
|
||||
#include <security.h>
|
||||
|
||||
DWORD osMajorVersion, osMinorVersion, osPlatformId;
|
||||
|
||||
char *platform_get_x_display(void) {
|
||||
/* We may as well check for DISPLAY in case it's useful. */
|
||||
return dupstr(getenv("DISPLAY"));
|
||||
}
|
||||
|
||||
Filename *filename_from_str(const char *str)
|
||||
{
|
||||
Filename *ret = snew(Filename);
|
||||
ret->path = dupstr(str);
|
||||
return ret;
|
||||
}
|
||||
|
||||
Filename *filename_copy(const Filename *fn)
|
||||
{
|
||||
return filename_from_str(fn->path);
|
||||
}
|
||||
|
||||
const char *filename_to_str(const Filename *fn)
|
||||
{
|
||||
return fn->path;
|
||||
}
|
||||
|
||||
bool filename_equal(const Filename *f1, const Filename *f2)
|
||||
{
|
||||
return !strcmp(f1->path, f2->path);
|
||||
}
|
||||
|
||||
bool filename_is_null(const Filename *fn)
|
||||
{
|
||||
return !*fn->path;
|
||||
}
|
||||
|
||||
void filename_free(Filename *fn)
|
||||
{
|
||||
sfree(fn->path);
|
||||
sfree(fn);
|
||||
}
|
||||
|
||||
void filename_serialise(BinarySink *bs, const Filename *f)
|
||||
{
|
||||
put_asciz(bs, f->path);
|
||||
}
|
||||
Filename *filename_deserialise(BinarySource *src)
|
||||
{
|
||||
return filename_from_str(get_asciz(src));
|
||||
}
|
||||
|
||||
char filename_char_sanitise(char c)
|
||||
{
|
||||
if (strchr("<>:\"/\\|?*", c))
|
||||
return '.';
|
||||
return c;
|
||||
}
|
||||
|
||||
char *get_username(void)
|
||||
{
|
||||
DWORD namelen;
|
||||
char *user;
|
||||
bool got_username = false;
|
||||
DECL_WINDOWS_FUNCTION(static, BOOLEAN, GetUserNameExA,
|
||||
(EXTENDED_NAME_FORMAT, LPSTR, PULONG));
|
||||
|
||||
{
|
||||
static bool tried_usernameex = false;
|
||||
if (!tried_usernameex) {
|
||||
/* Not available on Win9x, so load dynamically */
|
||||
HMODULE secur32 = load_system32_dll("secur32.dll");
|
||||
/* If MIT Kerberos is installed, the following call to
|
||||
GET_WINDOWS_FUNCTION makes Windows implicitly load
|
||||
sspicli.dll WITHOUT proper path sanitizing, so better
|
||||
load it properly before */
|
||||
HMODULE sspicli = load_system32_dll("sspicli.dll");
|
||||
(void)sspicli; /* squash compiler warning about unused variable */
|
||||
GET_WINDOWS_FUNCTION(secur32, GetUserNameExA);
|
||||
tried_usernameex = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (p_GetUserNameExA) {
|
||||
/*
|
||||
* If available, use the principal -- this avoids the problem
|
||||
* that the local username is case-insensitive but Kerberos
|
||||
* usernames are case-sensitive.
|
||||
*/
|
||||
|
||||
/* Get the length */
|
||||
namelen = 0;
|
||||
(void) p_GetUserNameExA(NameUserPrincipal, NULL, &namelen);
|
||||
|
||||
user = snewn(namelen, char);
|
||||
got_username = p_GetUserNameExA(NameUserPrincipal, user, &namelen);
|
||||
if (got_username) {
|
||||
char *p = strchr(user, '@');
|
||||
if (p) *p = 0;
|
||||
} else {
|
||||
sfree(user);
|
||||
}
|
||||
}
|
||||
|
||||
if (!got_username) {
|
||||
/* Fall back to local user name */
|
||||
namelen = 0;
|
||||
if (!GetUserName(NULL, &namelen)) {
|
||||
/*
|
||||
* Apparently this doesn't work at least on Windows XP SP2.
|
||||
* Thus assume a maximum of 256. It will fail again if it
|
||||
* doesn't fit.
|
||||
*/
|
||||
namelen = 256;
|
||||
}
|
||||
|
||||
user = snewn(namelen, char);
|
||||
got_username = GetUserName(user, &namelen);
|
||||
if (!got_username) {
|
||||
sfree(user);
|
||||
}
|
||||
}
|
||||
|
||||
return got_username ? user : NULL;
|
||||
}
|
||||
|
||||
void dll_hijacking_protection(void)
|
||||
{
|
||||
/*
|
||||
* If the OS provides it, call SetDefaultDllDirectories() to
|
||||
* prevent DLLs from being loaded from the directory containing
|
||||
* our own binary, and instead only load from system32.
|
||||
*
|
||||
* This is a protection against hijacking attacks, if someone runs
|
||||
* PuTTY directly from their web browser's download directory
|
||||
* having previously been enticed into clicking on an unwise link
|
||||
* that downloaded a malicious DLL to the same directory under one
|
||||
* of various magic names that seem to be things that standard
|
||||
* Windows DLLs delegate to.
|
||||
*
|
||||
* It shouldn't break deliberate loading of user-provided DLLs
|
||||
* such as GSSAPI providers, because those are specified by their
|
||||
* full pathname by the user-provided configuration.
|
||||
*/
|
||||
static HMODULE kernel32_module;
|
||||
DECL_WINDOWS_FUNCTION(static, BOOL, SetDefaultDllDirectories, (DWORD));
|
||||
|
||||
if (!kernel32_module) {
|
||||
kernel32_module = load_system32_dll("kernel32.dll");
|
||||
#if (defined _MSC_VER && _MSC_VER < 1900)
|
||||
/* For older Visual Studio, this function isn't available in
|
||||
* the header files to type-check */
|
||||
GET_WINDOWS_FUNCTION_NO_TYPECHECK(
|
||||
kernel32_module, SetDefaultDllDirectories);
|
||||
#else
|
||||
GET_WINDOWS_FUNCTION(kernel32_module, SetDefaultDllDirectories);
|
||||
#endif
|
||||
}
|
||||
|
||||
if (p_SetDefaultDllDirectories) {
|
||||
/* LOAD_LIBRARY_SEARCH_SYSTEM32 and explicitly specified
|
||||
* directories only */
|
||||
p_SetDefaultDllDirectories(LOAD_LIBRARY_SEARCH_SYSTEM32 |
|
||||
LOAD_LIBRARY_SEARCH_USER_DIRS);
|
||||
}
|
||||
}
|
||||
|
||||
void init_winver(void)
|
||||
{
|
||||
OSVERSIONINFO osVersion;
|
||||
static HMODULE kernel32_module;
|
||||
DECL_WINDOWS_FUNCTION(static, BOOL, GetVersionExA, (LPOSVERSIONINFO));
|
||||
|
||||
if (!kernel32_module) {
|
||||
kernel32_module = load_system32_dll("kernel32.dll");
|
||||
/* Deliberately don't type-check this function, because that
|
||||
* would involve using its declaration in a header file which
|
||||
* triggers a deprecation warning. I know it's deprecated (see
|
||||
* below) and don't need telling. */
|
||||
GET_WINDOWS_FUNCTION_NO_TYPECHECK(kernel32_module, GetVersionExA);
|
||||
}
|
||||
|
||||
ZeroMemory(&osVersion, sizeof(osVersion));
|
||||
osVersion.dwOSVersionInfoSize = sizeof (OSVERSIONINFO);
|
||||
if (p_GetVersionExA && p_GetVersionExA(&osVersion)) {
|
||||
osMajorVersion = osVersion.dwMajorVersion;
|
||||
osMinorVersion = osVersion.dwMinorVersion;
|
||||
osPlatformId = osVersion.dwPlatformId;
|
||||
} else {
|
||||
/*
|
||||
* GetVersionEx is deprecated, so allow for it perhaps going
|
||||
* away in future API versions. If it's not there, simply
|
||||
* assume that's because Windows is too _new_, so fill in the
|
||||
* variables we care about to a value that will always compare
|
||||
* higher than any given test threshold.
|
||||
*
|
||||
* Normally we should be checking against the presence of a
|
||||
* specific function if possible in any case.
|
||||
*/
|
||||
osMajorVersion = osMinorVersion = UINT_MAX; /* a very high number */
|
||||
osPlatformId = VER_PLATFORM_WIN32_NT; /* not Win32s or Win95-like */
|
||||
}
|
||||
}
|
||||
|
||||
HMODULE load_system32_dll(const char *libname)
|
||||
{
|
||||
/*
|
||||
* Wrapper function to load a DLL out of c:\windows\system32
|
||||
* without going through the full DLL search path. (Hence no
|
||||
* attack is possible by placing a substitute DLL earlier on that
|
||||
* path.)
|
||||
*/
|
||||
static char *sysdir = NULL;
|
||||
static size_t sysdirsize = 0;
|
||||
char *fullpath;
|
||||
HMODULE ret;
|
||||
|
||||
if (!sysdir) {
|
||||
size_t len;
|
||||
while ((len = GetSystemDirectory(sysdir, sysdirsize)) >= sysdirsize)
|
||||
sgrowarray(sysdir, sysdirsize, len);
|
||||
}
|
||||
|
||||
fullpath = dupcat(sysdir, "\\", libname);
|
||||
ret = LoadLibrary(fullpath);
|
||||
sfree(fullpath);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* A tree234 containing mappings from system error codes to strings.
|
||||
*/
|
||||
|
||||
struct errstring {
|
||||
int error;
|
||||
char *text;
|
||||
};
|
||||
|
||||
static int errstring_find(void *av, void *bv)
|
||||
{
|
||||
int *a = (int *)av;
|
||||
struct errstring *b = (struct errstring *)bv;
|
||||
if (*a < b->error)
|
||||
return -1;
|
||||
if (*a > b->error)
|
||||
return +1;
|
||||
return 0;
|
||||
}
|
||||
static int errstring_compare(void *av, void *bv)
|
||||
{
|
||||
struct errstring *a = (struct errstring *)av;
|
||||
return errstring_find(&a->error, bv);
|
||||
}
|
||||
|
||||
static tree234 *errstrings = NULL;
|
||||
|
||||
const char *win_strerror(int error)
|
||||
{
|
||||
struct errstring *es;
|
||||
|
||||
if (!errstrings)
|
||||
errstrings = newtree234(errstring_compare);
|
||||
|
||||
es = find234(errstrings, &error, errstring_find);
|
||||
|
||||
if (!es) {
|
||||
char msgtext[65536]; /* maximum size for FormatMessage is 64K */
|
||||
|
||||
es = snew(struct errstring);
|
||||
es->error = error;
|
||||
if (!FormatMessage((FORMAT_MESSAGE_FROM_SYSTEM |
|
||||
FORMAT_MESSAGE_IGNORE_INSERTS), NULL, error,
|
||||
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
|
||||
msgtext, lenof(msgtext)-1, NULL)) {
|
||||
sprintf(msgtext,
|
||||
"(unable to format: FormatMessage returned %u)",
|
||||
(unsigned int)GetLastError());
|
||||
} else {
|
||||
int len = strlen(msgtext);
|
||||
if (len > 0 && msgtext[len-1] == '\n')
|
||||
msgtext[len-1] = '\0';
|
||||
}
|
||||
es->text = dupprintf("Error %d: %s", error, msgtext);
|
||||
add234(errstrings, es);
|
||||
}
|
||||
|
||||
return es->text;
|
||||
}
|
||||
|
||||
FontSpec *fontspec_new(const char *name, bool bold, int height, int charset)
|
||||
{
|
||||
FontSpec *f = snew(FontSpec);
|
||||
f->name = dupstr(name);
|
||||
f->isbold = bold;
|
||||
f->height = height;
|
||||
f->charset = charset;
|
||||
return f;
|
||||
}
|
||||
FontSpec *fontspec_copy(const FontSpec *f)
|
||||
{
|
||||
return fontspec_new(f->name, f->isbold, f->height, f->charset);
|
||||
}
|
||||
void fontspec_free(FontSpec *f)
|
||||
{
|
||||
sfree(f->name);
|
||||
sfree(f);
|
||||
}
|
||||
void fontspec_serialise(BinarySink *bs, FontSpec *f)
|
||||
{
|
||||
put_asciz(bs, f->name);
|
||||
put_uint32(bs, f->isbold);
|
||||
put_uint32(bs, f->height);
|
||||
put_uint32(bs, f->charset);
|
||||
}
|
||||
FontSpec *fontspec_deserialise(BinarySource *src)
|
||||
{
|
||||
const char *name = get_asciz(src);
|
||||
unsigned isbold = get_uint32(src);
|
||||
unsigned height = get_uint32(src);
|
||||
unsigned charset = get_uint32(src);
|
||||
return fontspec_new(name, isbold, height, charset);
|
||||
}
|
||||
|
||||
bool open_for_write_would_lose_data(const Filename *fn)
|
||||
{
|
||||
WIN32_FILE_ATTRIBUTE_DATA attrs;
|
||||
if (!GetFileAttributesEx(fn->path, GetFileExInfoStandard, &attrs)) {
|
||||
/*
|
||||
* Generally, if we don't identify a specific reason why we
|
||||
* should return true from this function, we return false, and
|
||||
* let the subsequent attempt to open the file for real give a
|
||||
* more useful error message.
|
||||
*/
|
||||
return false;
|
||||
}
|
||||
if (attrs.dwFileAttributes & (FILE_ATTRIBUTE_DEVICE |
|
||||
FILE_ATTRIBUTE_DIRECTORY)) {
|
||||
/*
|
||||
* File is something other than an ordinary disk file, so
|
||||
* opening it for writing will not cause truncation. (It may
|
||||
* not _succeed_ either, but that's not our problem here!)
|
||||
*/
|
||||
return false;
|
||||
}
|
||||
if (attrs.nFileSizeHigh == 0 && attrs.nFileSizeLow == 0) {
|
||||
/*
|
||||
* File is zero-length (or may be a named pipe, which
|
||||
* dwFileAttributes can't tell apart from a regular file), so
|
||||
* opening it for writing won't truncate any data away because
|
||||
* there's nothing to truncate anyway.
|
||||
*/
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void escape_registry_key(const char *in, strbuf *out)
|
||||
{
|
||||
bool candot = false;
|
||||
static const char hex[16] = "0123456789ABCDEF";
|
||||
|
||||
while (*in) {
|
||||
if (*in == ' ' || *in == '\\' || *in == '*' || *in == '?' ||
|
||||
*in == '%' || *in < ' ' || *in > '~' || (*in == '.'
|
||||
&& !candot)) {
|
||||
put_byte(out, '%');
|
||||
put_byte(out, hex[((unsigned char) *in) >> 4]);
|
||||
put_byte(out, hex[((unsigned char) *in) & 15]);
|
||||
} else
|
||||
put_byte(out, *in);
|
||||
in++;
|
||||
candot = true;
|
||||
}
|
||||
}
|
||||
|
||||
void unescape_registry_key(const char *in, strbuf *out)
|
||||
{
|
||||
while (*in) {
|
||||
if (*in == '%' && in[1] && in[2]) {
|
||||
int i, j;
|
||||
|
||||
i = in[1] - '0';
|
||||
i -= (i > 9 ? 7 : 0);
|
||||
j = in[2] - '0';
|
||||
j -= (j > 9 ? 7 : 0);
|
||||
|
||||
put_byte(out, (i << 4) + j);
|
||||
in += 3;
|
||||
} else {
|
||||
put_byte(out, *in++);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
static FILE *debug_fp = NULL;
|
||||
static HANDLE debug_hdl = INVALID_HANDLE_VALUE;
|
||||
static int debug_got_console = 0;
|
||||
|
||||
void dputs(const char *buf)
|
||||
{
|
||||
DWORD dw;
|
||||
|
||||
if (!debug_got_console) {
|
||||
if (AllocConsole()) {
|
||||
debug_got_console = 1;
|
||||
debug_hdl = GetStdHandle(STD_OUTPUT_HANDLE);
|
||||
}
|
||||
}
|
||||
if (!debug_fp) {
|
||||
debug_fp = fopen("debug.log", "w");
|
||||
}
|
||||
|
||||
if (debug_hdl != INVALID_HANDLE_VALUE) {
|
||||
WriteFile(debug_hdl, buf, strlen(buf), &dw, NULL);
|
||||
}
|
||||
fputs(buf, debug_fp);
|
||||
fflush(debug_fp);
|
||||
}
|
||||
#endif
|
||||
|
||||
char *registry_get_string(HKEY root, const char *path, const char *leaf)
|
||||
{
|
||||
HKEY key = root;
|
||||
bool need_close_key = false;
|
||||
char *toret = NULL, *str = NULL;
|
||||
|
||||
if (path) {
|
||||
if (RegCreateKey(key, path, &key) != ERROR_SUCCESS)
|
||||
goto out;
|
||||
need_close_key = true;
|
||||
}
|
||||
|
||||
DWORD type, size;
|
||||
if (RegQueryValueEx(key, leaf, 0, &type, NULL, &size) != ERROR_SUCCESS)
|
||||
goto out;
|
||||
if (type != REG_SZ)
|
||||
goto out;
|
||||
|
||||
str = snewn(size + 1, char);
|
||||
DWORD size_got = size;
|
||||
if (RegQueryValueEx(key, leaf, 0, &type, (LPBYTE)str,
|
||||
&size_got) != ERROR_SUCCESS)
|
||||
goto out;
|
||||
if (type != REG_SZ || size_got > size)
|
||||
goto out;
|
||||
str[size_got] = '\0';
|
||||
|
||||
toret = str;
|
||||
str = NULL;
|
||||
|
||||
out:
|
||||
if (need_close_key)
|
||||
RegCloseKey(key);
|
||||
sfree(str);
|
||||
return toret;
|
||||
}
|
||||
@@ -31,7 +31,7 @@
|
||||
|
||||
#include "tree234.h"
|
||||
|
||||
#include "winhelp.h"
|
||||
#include "help.h"
|
||||
|
||||
#if defined _M_IX86 || defined _M_AMD64
|
||||
#define BUILDINFO_PLATFORM "x86 Windows"
|
||||
|
||||
Binary file not shown.
Reference in New Issue
Block a user