mirror of
https://git.tartarus.org/simon/putty.git
synced 2025-06-30 19:12:48 -05:00
New library-style 'utils' subdirectories.
Now that the new CMake build system is encouraging us to lay out the code like a set of libraries, it seems like a good idea to make them look more _like_ libraries, by putting things into separate modules as far as possible. This fixes several previous annoyances in which you had to link against some object in order to get a function you needed, but that object also contained other functions you didn't need which included link-time symbol references you didn't want to have to deal with. The usual offender was subsidiary supporting programs including misc.c for some innocuous function and then finding they had to deal with the requirements of buildinfo(). This big reorganisation introduces three new subdirectories called 'utils', one at the top level and one in each platform subdir. In each case, the directory contains basically the same files that were previously placed in the 'utils' build-time library, except that the ones that were extremely miscellaneous (misc.c, utils.c, uxmisc.c, winmisc.c, winmiscs.c, winutils.c) have been split up into much smaller pieces.
This commit is contained in:
39
windows/utils/arm_arch_queries.c
Normal file
39
windows/utils/arm_arch_queries.c
Normal file
@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Windows implementation of the OS query functions that detect Arm
|
||||
* architecture extensions.
|
||||
*/
|
||||
|
||||
#include "putty.h"
|
||||
|
||||
#if !(defined _M_ARM || defined _M_ARM64)
|
||||
/*
|
||||
* For non-Arm, stub out these functions. This module shouldn't be
|
||||
* _called_ in that situation anyway, but it will still be compiled
|
||||
* (because that's easier than getting CMake to identify the
|
||||
* architecture in all cases).
|
||||
*/
|
||||
#define IsProcessorFeaturePresent(...) false
|
||||
#endif
|
||||
|
||||
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;
|
||||
}
|
85
windows/utils/capi.c
Normal file
85
windows/utils/capi.c
Normal file
@ -0,0 +1,85 @@
|
||||
/*
|
||||
* windows/utils/capi.c: implementation of wincapi.h.
|
||||
*/
|
||||
|
||||
#include "putty.h"
|
||||
|
||||
#include "putty.h"
|
||||
#include "ssh.h"
|
||||
|
||||
#include "wincapi.h"
|
||||
|
||||
DEF_WINDOWS_FUNCTION(CryptProtectMemory);
|
||||
|
||||
bool got_crypt(void)
|
||||
{
|
||||
static bool attempted = false;
|
||||
static bool successful;
|
||||
static HMODULE crypt;
|
||||
|
||||
if (!attempted) {
|
||||
attempted = true;
|
||||
crypt = load_system32_dll("crypt32.dll");
|
||||
successful = crypt &&
|
||||
GET_WINDOWS_FUNCTION(crypt, CryptProtectMemory);
|
||||
}
|
||||
return successful;
|
||||
}
|
||||
|
||||
char *capi_obfuscate_string(const char *realname)
|
||||
{
|
||||
char *cryptdata;
|
||||
int cryptlen;
|
||||
unsigned char digest[32];
|
||||
char retbuf[65];
|
||||
int i;
|
||||
|
||||
cryptlen = strlen(realname) + 1;
|
||||
cryptlen += CRYPTPROTECTMEMORY_BLOCK_SIZE - 1;
|
||||
cryptlen /= CRYPTPROTECTMEMORY_BLOCK_SIZE;
|
||||
cryptlen *= CRYPTPROTECTMEMORY_BLOCK_SIZE;
|
||||
|
||||
cryptdata = snewn(cryptlen, char);
|
||||
memset(cryptdata, 0, cryptlen);
|
||||
strcpy(cryptdata, realname);
|
||||
|
||||
/*
|
||||
* CRYPTPROTECTMEMORY_CROSS_PROCESS causes CryptProtectMemory to
|
||||
* use the same key in all processes with this user id, meaning
|
||||
* that the next PuTTY process calling this function with the same
|
||||
* input will get the same data.
|
||||
*
|
||||
* (Contrast with CryptProtectData, which invents a new session
|
||||
* key every time since its API permits returning more data than
|
||||
* was input, so calling _that_ and hashing the output would not
|
||||
* be stable.)
|
||||
*
|
||||
* We don't worry too much if this doesn't work for some reason.
|
||||
* Omitting this step still has _some_ privacy value (in that
|
||||
* another user can test-hash things to confirm guesses as to
|
||||
* where you might be connecting to, but cannot invert SHA-256 in
|
||||
* the absence of any plausible guess). So we don't abort if we
|
||||
* can't call CryptProtectMemory at all, or if it fails.
|
||||
*/
|
||||
if (got_crypt())
|
||||
p_CryptProtectMemory(cryptdata, cryptlen,
|
||||
CRYPTPROTECTMEMORY_CROSS_PROCESS);
|
||||
|
||||
/*
|
||||
* We don't want to give away the length of the hostname either,
|
||||
* so having got it back out of CryptProtectMemory we now hash it.
|
||||
*/
|
||||
hash_simple(&ssh_sha256, make_ptrlen(cryptdata, cryptlen), digest);
|
||||
|
||||
sfree(cryptdata);
|
||||
|
||||
/*
|
||||
* Finally, make printable.
|
||||
*/
|
||||
for (i = 0; i < 32; i++) {
|
||||
sprintf(retbuf + 2*i, "%02x", digest[i]);
|
||||
/* the last of those will also write the trailing NUL */
|
||||
}
|
||||
|
||||
return dupstr(retbuf);
|
||||
}
|
40
windows/utils/defaults.c
Normal file
40
windows/utils/defaults.c
Normal file
@ -0,0 +1,40 @@
|
||||
/*
|
||||
* windows/utils/defaults.c: default settings that are specific to Windows.
|
||||
*/
|
||||
|
||||
#include "putty.h"
|
||||
|
||||
#include <commctrl.h>
|
||||
|
||||
FontSpec *platform_default_fontspec(const char *name)
|
||||
{
|
||||
if (!strcmp(name, "Font"))
|
||||
return fontspec_new("Courier New", false, 10, ANSI_CHARSET);
|
||||
else
|
||||
return fontspec_new("", false, 0, 0);
|
||||
}
|
||||
|
||||
Filename *platform_default_filename(const char *name)
|
||||
{
|
||||
if (!strcmp(name, "LogFileName"))
|
||||
return filename_from_str("putty.log");
|
||||
else
|
||||
return filename_from_str("");
|
||||
}
|
||||
|
||||
char *platform_default_s(const char *name)
|
||||
{
|
||||
if (!strcmp(name, "SerialLine"))
|
||||
return dupstr("COM1");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bool platform_default_b(const char *name, bool def)
|
||||
{
|
||||
return def;
|
||||
}
|
||||
|
||||
int platform_default_i(const char *name, int def)
|
||||
{
|
||||
return def;
|
||||
}
|
43
windows/utils/dll_hijacking_protection.c
Normal file
43
windows/utils/dll_hijacking_protection.c
Normal file
@ -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);
|
||||
}
|
||||
}
|
37
windows/utils/dputs.c
Normal file
37
windows/utils/dputs.c
Normal file
@ -0,0 +1,37 @@
|
||||
/*
|
||||
* 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 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);
|
||||
}
|
48
windows/utils/escape_registry_key.c
Normal file
48
windows/utils/escape_registry_key.c
Normal file
@ -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++);
|
||||
}
|
||||
}
|
||||
}
|
54
windows/utils/filename.c
Normal file
54
windows/utils/filename.c
Normal file
@ -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;
|
||||
}
|
43
windows/utils/fontspec.c
Normal file
43
windows/utils/fontspec.c
Normal file
@ -0,0 +1,43 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
77
windows/utils/get_username.c
Normal file
77
windows/utils/get_username.c
Normal file
@ -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;
|
||||
}
|
20
windows/utils/getdlgitemtext_alloc.c
Normal file
20
windows/utils/getdlgitemtext_alloc.c
Normal file
@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Handy wrapper around GetDlgItemText which doesn't make you invent
|
||||
* an arbitrary length limit on the output string. Returned string is
|
||||
* dynamically allocated; caller must free.
|
||||
*/
|
||||
|
||||
#include "putty.h"
|
||||
|
||||
char *GetDlgItemText_alloc(HWND hwnd, int id)
|
||||
{
|
||||
char *ret = NULL;
|
||||
size_t size = 0;
|
||||
|
||||
do {
|
||||
sgrowarray_nm(ret, size, size);
|
||||
GetDlgItemText(hwnd, id, ret, size);
|
||||
} while (!memchr(ret, '\0', size-1));
|
||||
|
||||
return ret;
|
||||
}
|
13
windows/utils/is_console_handle.c
Normal file
13
windows/utils/is_console_handle.c
Normal file
@ -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;
|
||||
}
|
26
windows/utils/load_system32_dll.c
Normal file
26
windows/utils/load_system32_dll.c
Normal file
@ -0,0 +1,26 @@
|
||||
/*
|
||||
* 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)
|
||||
{
|
||||
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;
|
||||
}
|
27
windows/utils/ltime.c
Normal file
27
windows/utils/ltime.c
Normal file
@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Implementation of ltime() that avoids trouble with time() returning
|
||||
* (time_t)-1 on Windows.
|
||||
*/
|
||||
|
||||
#include "putty.h"
|
||||
#include <time.h>
|
||||
|
||||
struct tm ltime(void)
|
||||
{
|
||||
SYSTEMTIME st;
|
||||
struct tm tm;
|
||||
|
||||
memset(&tm, 0, sizeof(tm)); /* in case there are any other fields */
|
||||
|
||||
GetLocalTime(&st);
|
||||
tm.tm_sec=st.wSecond;
|
||||
tm.tm_min=st.wMinute;
|
||||
tm.tm_hour=st.wHour;
|
||||
tm.tm_mday=st.wDay;
|
||||
tm.tm_mon=st.wMonth-1;
|
||||
tm.tm_year=(st.wYear>=1900?st.wYear-1900:0);
|
||||
tm.tm_wday=st.wDayOfWeek;
|
||||
tm.tm_yday=-1; /* GetLocalTime doesn't tell us */
|
||||
tm.tm_isdst=0; /* GetLocalTime doesn't tell us */
|
||||
return tm;
|
||||
}
|
19
windows/utils/makedlgitemborderless.c
Normal file
19
windows/utils/makedlgitemborderless.c
Normal file
@ -0,0 +1,19 @@
|
||||
/*
|
||||
* Helper function to remove the border around a dialog item such as
|
||||
* a read-only edit control.
|
||||
*/
|
||||
|
||||
#include "putty.h"
|
||||
|
||||
void MakeDlgItemBorderless(HWND parent, int id)
|
||||
{
|
||||
HWND child = GetDlgItem(parent, id);
|
||||
LONG_PTR style = GetWindowLongPtr(child, GWL_STYLE);
|
||||
LONG_PTR exstyle = GetWindowLongPtr(child, GWL_EXSTYLE);
|
||||
style &= ~WS_BORDER;
|
||||
exstyle &= ~(WS_EX_CLIENTEDGE | WS_EX_STATICEDGE | WS_EX_WINDOWEDGE);
|
||||
SetWindowLongPtr(child, GWL_STYLE, style);
|
||||
SetWindowLongPtr(child, GWL_EXSTYLE, exstyle);
|
||||
SetWindowPos(child, NULL, 0, 0, 0, 0,
|
||||
SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED);
|
||||
}
|
49
windows/utils/message_box.c
Normal file
49
windows/utils/message_box.c
Normal file
@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Message box with optional context help.
|
||||
*/
|
||||
|
||||
#include "putty.h"
|
||||
|
||||
static HWND message_box_owner;
|
||||
|
||||
/* Callback function to launch context help. */
|
||||
static VOID CALLBACK message_box_help_callback(LPHELPINFO lpHelpInfo)
|
||||
{
|
||||
const char *context = NULL;
|
||||
#define CHECK_CTX(name) \
|
||||
do { \
|
||||
if (lpHelpInfo->dwContextId == WINHELP_CTXID_ ## name) \
|
||||
context = WINHELP_CTX_ ## name; \
|
||||
} while (0)
|
||||
CHECK_CTX(errors_hostkey_absent);
|
||||
CHECK_CTX(errors_hostkey_changed);
|
||||
CHECK_CTX(errors_cantloadkey);
|
||||
CHECK_CTX(option_cleanup);
|
||||
CHECK_CTX(pgp_fingerprints);
|
||||
#undef CHECK_CTX
|
||||
if (context)
|
||||
launch_help(message_box_owner, context);
|
||||
}
|
||||
|
||||
int message_box(HWND owner, LPCTSTR text, LPCTSTR caption,
|
||||
DWORD style, DWORD helpctxid)
|
||||
{
|
||||
MSGBOXPARAMS mbox;
|
||||
|
||||
/*
|
||||
* We use MessageBoxIndirect() because it allows us to specify a
|
||||
* callback function for the Help button.
|
||||
*/
|
||||
mbox.cbSize = sizeof(mbox);
|
||||
/* Assumes the globals `hinst' and `hwnd' have sensible values. */
|
||||
mbox.hInstance = hinst;
|
||||
mbox.hwndOwner = message_box_owner = owner;
|
||||
mbox.lpfnMsgBoxCallback = &message_box_help_callback;
|
||||
mbox.dwLanguageId = LANG_NEUTRAL;
|
||||
mbox.lpszText = text;
|
||||
mbox.lpszCaption = caption;
|
||||
mbox.dwContextHelpId = helpctxid;
|
||||
mbox.dwStyle = style;
|
||||
if (helpctxid != 0 && has_help()) mbox.dwStyle |= MB_HELP;
|
||||
return MessageBoxIndirect(&mbox);
|
||||
}
|
227
windows/utils/minefield.c
Normal file
227
windows/utils/minefield.c
Normal file
@ -0,0 +1,227 @@
|
||||
/*
|
||||
* '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"
|
||||
|
||||
#define PAGESIZE 4096
|
||||
|
||||
/*
|
||||
* Design:
|
||||
*
|
||||
* We start by reserving as much virtual address space as Windows
|
||||
* will sensibly (or not sensibly) let us have. We flag it all as
|
||||
* invalid memory.
|
||||
*
|
||||
* Any allocation attempt is satisfied by committing one or more
|
||||
* pages, with an uncommitted page on either side. The returned
|
||||
* memory region is jammed up against the _end_ of the pages.
|
||||
*
|
||||
* Freeing anything causes instantaneous decommitment of the pages
|
||||
* involved, so stale pointers are caught as soon as possible.
|
||||
*/
|
||||
|
||||
static int minefield_initialised = 0;
|
||||
static void *minefield_region = NULL;
|
||||
static long minefield_size = 0;
|
||||
static long minefield_npages = 0;
|
||||
static long minefield_curpos = 0;
|
||||
static unsigned short *minefield_admin = NULL;
|
||||
static void *minefield_pages = NULL;
|
||||
|
||||
static void minefield_admin_hide(int hide)
|
||||
{
|
||||
int access = hide ? PAGE_NOACCESS : PAGE_READWRITE;
|
||||
VirtualProtect(minefield_admin, minefield_npages * 2, access, NULL);
|
||||
}
|
||||
|
||||
static void minefield_init(void)
|
||||
{
|
||||
int size;
|
||||
int admin_size;
|
||||
int i;
|
||||
|
||||
for (size = 0x40000000; size > 0; size = ((size >> 3) * 7) & ~0xFFF) {
|
||||
minefield_region = VirtualAlloc(NULL, size,
|
||||
MEM_RESERVE, PAGE_NOACCESS);
|
||||
if (minefield_region)
|
||||
break;
|
||||
}
|
||||
minefield_size = size;
|
||||
|
||||
/*
|
||||
* Firstly, allocate a section of that to be the admin block.
|
||||
* We'll need a two-byte field for each page.
|
||||
*/
|
||||
minefield_admin = minefield_region;
|
||||
minefield_npages = minefield_size / PAGESIZE;
|
||||
admin_size = (minefield_npages * 2 + PAGESIZE - 1) & ~(PAGESIZE - 1);
|
||||
minefield_npages = (minefield_size - admin_size) / PAGESIZE;
|
||||
minefield_pages = (char *) minefield_region + admin_size;
|
||||
|
||||
/*
|
||||
* Commit the admin region.
|
||||
*/
|
||||
VirtualAlloc(minefield_admin, minefield_npages * 2,
|
||||
MEM_COMMIT, PAGE_READWRITE);
|
||||
|
||||
/*
|
||||
* Mark all pages as unused (0xFFFF).
|
||||
*/
|
||||
for (i = 0; i < minefield_npages; i++)
|
||||
minefield_admin[i] = 0xFFFF;
|
||||
|
||||
/*
|
||||
* Hide the admin region.
|
||||
*/
|
||||
minefield_admin_hide(1);
|
||||
|
||||
minefield_initialised = 1;
|
||||
}
|
||||
|
||||
static void minefield_bomb(void)
|
||||
{
|
||||
div(1, *(int *) minefield_pages);
|
||||
}
|
||||
|
||||
static void *minefield_alloc(int size)
|
||||
{
|
||||
int npages;
|
||||
int pos, lim, region_end, region_start;
|
||||
int start;
|
||||
int i;
|
||||
|
||||
npages = (size + PAGESIZE - 1) / PAGESIZE;
|
||||
|
||||
minefield_admin_hide(0);
|
||||
|
||||
/*
|
||||
* Search from current position until we find a contiguous
|
||||
* bunch of npages+2 unused pages.
|
||||
*/
|
||||
pos = minefield_curpos;
|
||||
lim = minefield_npages;
|
||||
while (1) {
|
||||
/* Skip over used pages. */
|
||||
while (pos < lim && minefield_admin[pos] != 0xFFFF)
|
||||
pos++;
|
||||
/* Count unused pages. */
|
||||
start = pos;
|
||||
while (pos < lim && pos - start < npages + 2 &&
|
||||
minefield_admin[pos] == 0xFFFF)
|
||||
pos++;
|
||||
if (pos - start == npages + 2)
|
||||
break;
|
||||
/* If we've reached the limit, reset the limit or stop. */
|
||||
if (pos >= lim) {
|
||||
if (lim == minefield_npages) {
|
||||
/* go round and start again at zero */
|
||||
lim = minefield_curpos;
|
||||
pos = 0;
|
||||
} else {
|
||||
minefield_admin_hide(1);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
minefield_curpos = pos - 1;
|
||||
|
||||
/*
|
||||
* We have npages+2 unused pages starting at start. We leave
|
||||
* the first and last of these alone and use the rest.
|
||||
*/
|
||||
region_end = (start + npages + 1) * PAGESIZE;
|
||||
region_start = region_end - size;
|
||||
/* FIXME: could align here if we wanted */
|
||||
|
||||
/*
|
||||
* Update the admin region.
|
||||
*/
|
||||
for (i = start + 2; i < start + npages + 1; i++)
|
||||
minefield_admin[i] = 0xFFFE; /* used but no region starts here */
|
||||
minefield_admin[start + 1] = region_start % PAGESIZE;
|
||||
|
||||
minefield_admin_hide(1);
|
||||
|
||||
VirtualAlloc((char *) minefield_pages + region_start, size,
|
||||
MEM_COMMIT, PAGE_READWRITE);
|
||||
return (char *) minefield_pages + region_start;
|
||||
}
|
||||
|
||||
static void minefield_free(void *ptr)
|
||||
{
|
||||
int region_start, i, j;
|
||||
|
||||
minefield_admin_hide(0);
|
||||
|
||||
region_start = (char *) ptr - (char *) minefield_pages;
|
||||
i = region_start / PAGESIZE;
|
||||
if (i < 0 || i >= minefield_npages ||
|
||||
minefield_admin[i] != region_start % PAGESIZE)
|
||||
minefield_bomb();
|
||||
for (j = i; j < minefield_npages && minefield_admin[j] != 0xFFFF; j++) {
|
||||
minefield_admin[j] = 0xFFFF;
|
||||
}
|
||||
|
||||
VirtualFree(ptr, j * PAGESIZE - region_start, MEM_DECOMMIT);
|
||||
|
||||
minefield_admin_hide(1);
|
||||
}
|
||||
|
||||
static int minefield_get_size(void *ptr)
|
||||
{
|
||||
int region_start, i, j;
|
||||
|
||||
minefield_admin_hide(0);
|
||||
|
||||
region_start = (char *) ptr - (char *) minefield_pages;
|
||||
i = region_start / PAGESIZE;
|
||||
if (i < 0 || i >= minefield_npages ||
|
||||
minefield_admin[i] != region_start % PAGESIZE)
|
||||
minefield_bomb();
|
||||
for (j = i; j < minefield_npages && minefield_admin[j] != 0xFFFF; j++);
|
||||
|
||||
minefield_admin_hide(1);
|
||||
|
||||
return j * PAGESIZE - region_start;
|
||||
}
|
||||
|
||||
void *minefield_c_malloc(size_t size)
|
||||
{
|
||||
if (!minefield_initialised)
|
||||
minefield_init();
|
||||
return minefield_alloc(size);
|
||||
}
|
||||
|
||||
void minefield_c_free(void *p)
|
||||
{
|
||||
if (!minefield_initialised)
|
||||
minefield_init();
|
||||
minefield_free(p);
|
||||
}
|
||||
|
||||
/*
|
||||
* realloc _always_ moves the chunk, for rapid detection of code
|
||||
* that assumes it won't.
|
||||
*/
|
||||
void *minefield_c_realloc(void *p, size_t size)
|
||||
{
|
||||
size_t oldsize;
|
||||
void *q;
|
||||
if (!minefield_initialised)
|
||||
minefield_init();
|
||||
q = minefield_alloc(size);
|
||||
oldsize = minefield_get_size(p);
|
||||
memcpy(q, p, (oldsize < size ? oldsize : size));
|
||||
minefield_free(p);
|
||||
return q;
|
||||
}
|
38
windows/utils/open_for_write_would_lose_data.c
Normal file
38
windows/utils/open_for_write_would_lose_data.c
Normal file
@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Implementation of open_for_write_would_lose_data for Windows.
|
||||
*/
|
||||
|
||||
#include "putty.h"
|
||||
|
||||
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;
|
||||
}
|
25
windows/utils/pgp_fingerprints_msgbox.c
Normal file
25
windows/utils/pgp_fingerprints_msgbox.c
Normal file
@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Display the fingerprints of the PGP Master Keys to the user as a
|
||||
* GUI message box.
|
||||
*/
|
||||
|
||||
#include "putty.h"
|
||||
|
||||
void pgp_fingerprints_msgbox(HWND owner)
|
||||
{
|
||||
message_box(
|
||||
owner,
|
||||
"These are the fingerprints of the PuTTY PGP Master Keys. They can\n"
|
||||
"be used to establish a trust path from this executable to another\n"
|
||||
"one. See the manual for more information.\n"
|
||||
"(Note: these fingerprints have nothing to do with SSH!)\n"
|
||||
"\n"
|
||||
"PuTTY Master Key as of " PGP_MASTER_KEY_YEAR
|
||||
" (" PGP_MASTER_KEY_DETAILS "):\n"
|
||||
" " PGP_MASTER_KEY_FP "\n\n"
|
||||
"Previous Master Key (" PGP_PREV_MASTER_KEY_YEAR
|
||||
", " PGP_PREV_MASTER_KEY_DETAILS "):\n"
|
||||
" " PGP_PREV_MASTER_KEY_FP,
|
||||
"PGP fingerprints", MB_ICONINFORMATION | MB_OK,
|
||||
HELPCTXID(pgp_fingerprints));
|
||||
}
|
12
windows/utils/platform_get_x_display.c
Normal file
12
windows/utils/platform_get_x_display.c
Normal file
@ -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"));
|
||||
}
|
43
windows/utils/registry_get_string.c
Normal file
43
windows/utils/registry_get_string.c
Normal file
@ -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;
|
||||
}
|
71
windows/utils/request_file.c
Normal file
71
windows/utils/request_file.c
Normal file
@ -0,0 +1,71 @@
|
||||
/*
|
||||
* GetOpenFileName/GetSaveFileName tend to muck around with the process'
|
||||
* working directory on at least some versions of Windows.
|
||||
* Here's a wrapper that gives more control over this, and hides a little
|
||||
* bit of other grottiness.
|
||||
*/
|
||||
|
||||
#include "putty.h"
|
||||
|
||||
struct filereq_tag {
|
||||
TCHAR cwd[MAX_PATH];
|
||||
};
|
||||
|
||||
/*
|
||||
* `of' is expected to be initialised with most interesting fields, but
|
||||
* this function does some administrivia. (assume `of' was memset to 0)
|
||||
* save==1 -> GetSaveFileName; save==0 -> GetOpenFileName
|
||||
* `state' is optional.
|
||||
*/
|
||||
bool request_file(filereq *state, OPENFILENAME *of, bool preserve, bool save)
|
||||
{
|
||||
TCHAR cwd[MAX_PATH]; /* process CWD */
|
||||
bool ret;
|
||||
|
||||
/* Get process CWD */
|
||||
if (preserve) {
|
||||
DWORD r = GetCurrentDirectory(lenof(cwd), cwd);
|
||||
if (r == 0 || r >= lenof(cwd))
|
||||
/* Didn't work, oh well. Stop trying to be clever. */
|
||||
preserve = false;
|
||||
}
|
||||
|
||||
/* Open the file requester, maybe setting lpstrInitialDir */
|
||||
{
|
||||
#ifdef OPENFILENAME_SIZE_VERSION_400
|
||||
of->lStructSize = OPENFILENAME_SIZE_VERSION_400;
|
||||
#else
|
||||
of->lStructSize = sizeof(*of);
|
||||
#endif
|
||||
of->lpstrInitialDir = (state && state->cwd[0]) ? state->cwd : NULL;
|
||||
/* Actually put up the requester. */
|
||||
ret = save ? GetSaveFileName(of) : GetOpenFileName(of);
|
||||
}
|
||||
|
||||
/* Get CWD left by requester */
|
||||
if (state) {
|
||||
DWORD r = GetCurrentDirectory(lenof(state->cwd), state->cwd);
|
||||
if (r == 0 || r >= lenof(state->cwd))
|
||||
/* Didn't work, oh well. */
|
||||
state->cwd[0] = '\0';
|
||||
}
|
||||
|
||||
/* Restore process CWD */
|
||||
if (preserve)
|
||||
/* If it fails, there's not much we can do. */
|
||||
(void) SetCurrentDirectory(cwd);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
filereq *filereq_new(void)
|
||||
{
|
||||
filereq *ret = snew(filereq);
|
||||
ret->cwd[0] = '\0';
|
||||
return ret;
|
||||
}
|
||||
|
||||
void filereq_free(filereq *state)
|
||||
{
|
||||
sfree(state);
|
||||
}
|
326
windows/utils/security.c
Normal file
326
windows/utils/security.c
Normal file
@ -0,0 +1,326 @@
|
||||
/*
|
||||
* windows/utils/security.c: implementation of winsecur.h.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "putty.h"
|
||||
|
||||
#include "winsecur.h"
|
||||
|
||||
/* Initialised once, then kept around to reuse forever */
|
||||
static PSID worldsid, networksid, usersid;
|
||||
|
||||
DEF_WINDOWS_FUNCTION(OpenProcessToken);
|
||||
DEF_WINDOWS_FUNCTION(GetTokenInformation);
|
||||
DEF_WINDOWS_FUNCTION(InitializeSecurityDescriptor);
|
||||
DEF_WINDOWS_FUNCTION(SetSecurityDescriptorOwner);
|
||||
DEF_WINDOWS_FUNCTION(GetSecurityInfo);
|
||||
DEF_WINDOWS_FUNCTION(SetSecurityInfo);
|
||||
DEF_WINDOWS_FUNCTION(SetEntriesInAclA);
|
||||
|
||||
bool got_advapi(void)
|
||||
{
|
||||
static bool attempted = false;
|
||||
static bool successful;
|
||||
static HMODULE advapi;
|
||||
|
||||
if (!attempted) {
|
||||
attempted = true;
|
||||
advapi = load_system32_dll("advapi32.dll");
|
||||
successful = advapi &&
|
||||
GET_WINDOWS_FUNCTION(advapi, GetSecurityInfo) &&
|
||||
GET_WINDOWS_FUNCTION(advapi, SetSecurityInfo) &&
|
||||
GET_WINDOWS_FUNCTION(advapi, OpenProcessToken) &&
|
||||
GET_WINDOWS_FUNCTION(advapi, GetTokenInformation) &&
|
||||
GET_WINDOWS_FUNCTION(advapi, InitializeSecurityDescriptor) &&
|
||||
GET_WINDOWS_FUNCTION(advapi, SetSecurityDescriptorOwner) &&
|
||||
GET_WINDOWS_FUNCTION(advapi, SetEntriesInAclA);
|
||||
}
|
||||
return successful;
|
||||
}
|
||||
|
||||
PSID get_user_sid(void)
|
||||
{
|
||||
HANDLE proc = NULL, tok = NULL;
|
||||
TOKEN_USER *user = NULL;
|
||||
DWORD toklen, sidlen;
|
||||
PSID sid = NULL, ret = NULL;
|
||||
|
||||
if (usersid)
|
||||
return usersid;
|
||||
|
||||
if (!got_advapi())
|
||||
goto cleanup;
|
||||
|
||||
if ((proc = OpenProcess(MAXIMUM_ALLOWED, false,
|
||||
GetCurrentProcessId())) == NULL)
|
||||
goto cleanup;
|
||||
|
||||
if (!p_OpenProcessToken(proc, TOKEN_QUERY, &tok))
|
||||
goto cleanup;
|
||||
|
||||
if (!p_GetTokenInformation(tok, TokenUser, NULL, 0, &toklen) &&
|
||||
GetLastError() != ERROR_INSUFFICIENT_BUFFER)
|
||||
goto cleanup;
|
||||
|
||||
if ((user = (TOKEN_USER *)LocalAlloc(LPTR, toklen)) == NULL)
|
||||
goto cleanup;
|
||||
|
||||
if (!p_GetTokenInformation(tok, TokenUser, user, toklen, &toklen))
|
||||
goto cleanup;
|
||||
|
||||
sidlen = GetLengthSid(user->User.Sid);
|
||||
|
||||
sid = (PSID)smalloc(sidlen);
|
||||
|
||||
if (!CopySid(sidlen, sid, user->User.Sid))
|
||||
goto cleanup;
|
||||
|
||||
/* Success. Move sid into the return value slot, and null it out
|
||||
* to stop the cleanup code freeing it. */
|
||||
ret = usersid = sid;
|
||||
sid = NULL;
|
||||
|
||||
cleanup:
|
||||
if (proc != NULL)
|
||||
CloseHandle(proc);
|
||||
if (tok != NULL)
|
||||
CloseHandle(tok);
|
||||
if (user != NULL)
|
||||
LocalFree(user);
|
||||
if (sid != NULL)
|
||||
sfree(sid);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static bool getsids(char **error)
|
||||
{
|
||||
#ifdef __clang__
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wmissing-braces"
|
||||
#endif
|
||||
SID_IDENTIFIER_AUTHORITY world_auth = SECURITY_WORLD_SID_AUTHORITY;
|
||||
SID_IDENTIFIER_AUTHORITY nt_auth = SECURITY_NT_AUTHORITY;
|
||||
#ifdef __clang__
|
||||
#pragma clang diagnostic pop
|
||||
#endif
|
||||
|
||||
bool ret = false;
|
||||
|
||||
*error = NULL;
|
||||
|
||||
if (!usersid) {
|
||||
if ((usersid = get_user_sid()) == NULL) {
|
||||
*error = dupprintf("unable to construct SID for current user: %s",
|
||||
win_strerror(GetLastError()));
|
||||
goto cleanup;
|
||||
}
|
||||
}
|
||||
|
||||
if (!worldsid) {
|
||||
if (!AllocateAndInitializeSid(&world_auth, 1, SECURITY_WORLD_RID,
|
||||
0, 0, 0, 0, 0, 0, 0, &worldsid)) {
|
||||
*error = dupprintf("unable to construct SID for world: %s",
|
||||
win_strerror(GetLastError()));
|
||||
goto cleanup;
|
||||
}
|
||||
}
|
||||
|
||||
if (!networksid) {
|
||||
if (!AllocateAndInitializeSid(&nt_auth, 1, SECURITY_NETWORK_RID,
|
||||
0, 0, 0, 0, 0, 0, 0, &networksid)) {
|
||||
*error = dupprintf("unable to construct SID for "
|
||||
"local same-user access only: %s",
|
||||
win_strerror(GetLastError()));
|
||||
goto cleanup;
|
||||
}
|
||||
}
|
||||
|
||||
ret = true;
|
||||
|
||||
cleanup:
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
bool make_private_security_descriptor(DWORD permissions,
|
||||
PSECURITY_DESCRIPTOR *psd,
|
||||
PACL *acl,
|
||||
char **error)
|
||||
{
|
||||
EXPLICIT_ACCESS ea[3];
|
||||
int acl_err;
|
||||
bool ret = false;
|
||||
|
||||
|
||||
*psd = NULL;
|
||||
*acl = NULL;
|
||||
*error = NULL;
|
||||
|
||||
if (!getsids(error))
|
||||
goto cleanup;
|
||||
|
||||
memset(ea, 0, sizeof(ea));
|
||||
ea[0].grfAccessPermissions = permissions;
|
||||
ea[0].grfAccessMode = REVOKE_ACCESS;
|
||||
ea[0].grfInheritance = NO_INHERITANCE;
|
||||
ea[0].Trustee.TrusteeForm = TRUSTEE_IS_SID;
|
||||
ea[0].Trustee.ptstrName = (LPTSTR)worldsid;
|
||||
ea[1].grfAccessPermissions = permissions;
|
||||
ea[1].grfAccessMode = GRANT_ACCESS;
|
||||
ea[1].grfInheritance = NO_INHERITANCE;
|
||||
ea[1].Trustee.TrusteeForm = TRUSTEE_IS_SID;
|
||||
ea[1].Trustee.ptstrName = (LPTSTR)usersid;
|
||||
ea[2].grfAccessPermissions = permissions;
|
||||
ea[2].grfAccessMode = REVOKE_ACCESS;
|
||||
ea[2].grfInheritance = NO_INHERITANCE;
|
||||
ea[2].Trustee.TrusteeForm = TRUSTEE_IS_SID;
|
||||
ea[2].Trustee.ptstrName = (LPTSTR)networksid;
|
||||
|
||||
acl_err = p_SetEntriesInAclA(3, ea, NULL, acl);
|
||||
if (acl_err != ERROR_SUCCESS || *acl == NULL) {
|
||||
*error = dupprintf("unable to construct ACL: %s",
|
||||
win_strerror(acl_err));
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
*psd = (PSECURITY_DESCRIPTOR)
|
||||
LocalAlloc(LPTR, SECURITY_DESCRIPTOR_MIN_LENGTH);
|
||||
if (!*psd) {
|
||||
*error = dupprintf("unable to allocate security descriptor: %s",
|
||||
win_strerror(GetLastError()));
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (!InitializeSecurityDescriptor(*psd, SECURITY_DESCRIPTOR_REVISION)) {
|
||||
*error = dupprintf("unable to initialise security descriptor: %s",
|
||||
win_strerror(GetLastError()));
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (!SetSecurityDescriptorOwner(*psd, usersid, false)) {
|
||||
*error = dupprintf("unable to set owner in security descriptor: %s",
|
||||
win_strerror(GetLastError()));
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (!SetSecurityDescriptorDacl(*psd, true, *acl, false)) {
|
||||
*error = dupprintf("unable to set DACL in security descriptor: %s",
|
||||
win_strerror(GetLastError()));
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
ret = true;
|
||||
|
||||
cleanup:
|
||||
if (!ret) {
|
||||
if (*psd) {
|
||||
LocalFree(*psd);
|
||||
*psd = NULL;
|
||||
}
|
||||
if (*acl) {
|
||||
LocalFree(*acl);
|
||||
*acl = NULL;
|
||||
}
|
||||
} else {
|
||||
sfree(*error);
|
||||
*error = NULL;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static bool acl_restricted = false;
|
||||
bool restricted_acl(void) { return acl_restricted; }
|
||||
|
||||
static bool really_restrict_process_acl(char **error)
|
||||
{
|
||||
EXPLICIT_ACCESS ea[2];
|
||||
int acl_err;
|
||||
bool ret = false;
|
||||
PACL acl = NULL;
|
||||
|
||||
static const DWORD nastyace=WRITE_DAC | WRITE_OWNER |
|
||||
PROCESS_CREATE_PROCESS | PROCESS_CREATE_THREAD |
|
||||
PROCESS_DUP_HANDLE |
|
||||
PROCESS_SET_QUOTA | PROCESS_SET_INFORMATION |
|
||||
PROCESS_VM_OPERATION | PROCESS_VM_READ | PROCESS_VM_WRITE |
|
||||
PROCESS_SUSPEND_RESUME;
|
||||
|
||||
if (!getsids(error))
|
||||
goto cleanup;
|
||||
|
||||
memset(ea, 0, sizeof(ea));
|
||||
|
||||
/* Everyone: deny */
|
||||
ea[0].grfAccessPermissions = nastyace;
|
||||
ea[0].grfAccessMode = DENY_ACCESS;
|
||||
ea[0].grfInheritance = SUB_CONTAINERS_AND_OBJECTS_INHERIT;
|
||||
ea[0].Trustee.TrusteeForm = TRUSTEE_IS_SID;
|
||||
ea[0].Trustee.ptstrName = (LPTSTR)worldsid;
|
||||
|
||||
/* User: user ace */
|
||||
ea[1].grfAccessPermissions = ~nastyace & 0x1fff;
|
||||
ea[1].grfAccessMode = GRANT_ACCESS;
|
||||
ea[1].grfInheritance = SUB_CONTAINERS_AND_OBJECTS_INHERIT;
|
||||
ea[1].Trustee.TrusteeForm = TRUSTEE_IS_SID;
|
||||
ea[1].Trustee.ptstrName = (LPTSTR)usersid;
|
||||
|
||||
acl_err = p_SetEntriesInAclA(2, ea, NULL, &acl);
|
||||
|
||||
if (acl_err != ERROR_SUCCESS || acl == NULL) {
|
||||
*error = dupprintf("unable to construct ACL: %s",
|
||||
win_strerror(acl_err));
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (ERROR_SUCCESS != p_SetSecurityInfo
|
||||
(GetCurrentProcess(), SE_KERNEL_OBJECT,
|
||||
OWNER_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION,
|
||||
usersid, NULL, acl, NULL)) {
|
||||
*error = dupprintf("Unable to set process ACL: %s",
|
||||
win_strerror(GetLastError()));
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
acl_restricted = true;
|
||||
ret=true;
|
||||
|
||||
cleanup:
|
||||
if (!ret) {
|
||||
if (acl) {
|
||||
LocalFree(acl);
|
||||
acl = NULL;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Lock down our process's ACL, to present an obstacle to malware
|
||||
* trying to write into its memory. This can't be a full defence,
|
||||
* because well timed malware could attack us before this code runs -
|
||||
* even if it was unconditionally run at the very start of main(),
|
||||
* which we wouldn't want to do anyway because it turns out in practie
|
||||
* that interfering with other processes in this way has significant
|
||||
* non-infringing uses on Windows (e.g. screen reader software).
|
||||
*
|
||||
* If we've been requested to do this and are unsuccessful, bomb out
|
||||
* via modalfatalbox rather than continue in a less protected mode.
|
||||
*
|
||||
* This function is intentionally outside the #ifndef NO_SECURITY that
|
||||
* covers the rest of this file, because when PuTTY is compiled
|
||||
* without the ability to restrict its ACL, we don't want it to
|
||||
* silently pretend to honour the instruction to do so.
|
||||
*/
|
||||
void restrict_process_acl(void)
|
||||
{
|
||||
char *error = NULL;
|
||||
bool ret;
|
||||
|
||||
ret = really_restrict_process_acl(&error);
|
||||
if (!ret)
|
||||
modalfatalbox("Could not restrict process ACL: %s", error);
|
||||
}
|
459
windows/utils/split_into_argv.c
Normal file
459
windows/utils/split_into_argv.c
Normal file
@ -0,0 +1,459 @@
|
||||
/*
|
||||
* Split a complete command line into argc/argv, attempting to do it
|
||||
* exactly the same way the Visual Studio C library would do it (so
|
||||
* that our console utilities, which receive argc and argv already
|
||||
* broken apart by the C library, will have their command lines
|
||||
* processed in the same way as the GUI utilities which get a whole
|
||||
* command line and must call this function).
|
||||
*
|
||||
* Does not modify the input command line.
|
||||
*
|
||||
* The final parameter (argstart) is used to return a second array
|
||||
* of char * pointers, the same length as argv, each one pointing
|
||||
* at the start of the corresponding element of argv in the
|
||||
* original command line. So if you get half way through processing
|
||||
* your command line in argc/argv form and then decide you want to
|
||||
* treat the rest as a raw string, you can. If you don't want to,
|
||||
* `argstart' can be safely left NULL.
|
||||
*/
|
||||
|
||||
#include "putty.h"
|
||||
|
||||
void split_into_argv(char *cmdline, int *argc, char ***argv,
|
||||
char ***argstart)
|
||||
{
|
||||
char *p;
|
||||
char *outputline, *q;
|
||||
char **outputargv, **outputargstart;
|
||||
int outputargc;
|
||||
|
||||
/*
|
||||
* These argument-breaking rules apply to Visual Studio 7, which
|
||||
* is currently the compiler expected to be used for PuTTY. Visual
|
||||
* Studio 10 has different rules, lacking the curious mod 3
|
||||
* behaviour of consecutive quotes described below; I presume they
|
||||
* fixed a bug. As and when we migrate to a newer compiler, we'll
|
||||
* have to adjust this to match; however, for the moment we
|
||||
* faithfully imitate in our GUI utilities what our CLI utilities
|
||||
* can't be prevented from doing.
|
||||
*
|
||||
* When I investigated this, at first glance the rules appeared to
|
||||
* be:
|
||||
*
|
||||
* - Single quotes are not special characters.
|
||||
*
|
||||
* - Double quotes are removed, but within them spaces cease
|
||||
* to be special.
|
||||
*
|
||||
* - Backslashes are _only_ special when a sequence of them
|
||||
* appear just before a double quote. In this situation,
|
||||
* they are treated like C backslashes: so \" just gives a
|
||||
* literal quote, \\" gives a literal backslash and then
|
||||
* opens or closes a double-quoted segment, \\\" gives a
|
||||
* literal backslash and then a literal quote, \\\\" gives
|
||||
* two literal backslashes and then opens/closes a
|
||||
* double-quoted segment, and so forth. Note that this
|
||||
* behaviour is identical inside and outside double quotes.
|
||||
*
|
||||
* - Two successive double quotes become one literal double
|
||||
* quote, but only _inside_ a double-quoted segment.
|
||||
* Outside, they just form an empty double-quoted segment
|
||||
* (which may cause an empty argument word).
|
||||
*
|
||||
* - That only leaves the interesting question of what happens
|
||||
* when one or more backslashes precedes two or more double
|
||||
* quotes, starting inside a double-quoted string. And the
|
||||
* answer to that appears somewhat bizarre. Here I tabulate
|
||||
* number of backslashes (across the top) against number of
|
||||
* quotes (down the left), and indicate how many backslashes
|
||||
* are output, how many quotes are output, and whether a
|
||||
* quoted segment is open at the end of the sequence:
|
||||
*
|
||||
* backslashes
|
||||
*
|
||||
* 0 1 2 3 4
|
||||
*
|
||||
* 0 0,0,y | 1,0,y 2,0,y 3,0,y 4,0,y
|
||||
* --------+-----------------------------
|
||||
* 1 0,0,n | 0,1,y 1,0,n 1,1,y 2,0,n
|
||||
* q 2 0,1,n | 0,1,n 1,1,n 1,1,n 2,1,n
|
||||
* u 3 0,1,y | 0,2,n 1,1,y 1,2,n 2,1,y
|
||||
* o 4 0,1,n | 0,2,y 1,1,n 1,2,y 2,1,n
|
||||
* t 5 0,2,n | 0,2,n 1,2,n 1,2,n 2,2,n
|
||||
* e 6 0,2,y | 0,3,n 1,2,y 1,3,n 2,2,y
|
||||
* s 7 0,2,n | 0,3,y 1,2,n 1,3,y 2,2,n
|
||||
* 8 0,3,n | 0,3,n 1,3,n 1,3,n 2,3,n
|
||||
* 9 0,3,y | 0,4,n 1,3,y 1,4,n 2,3,y
|
||||
* 10 0,3,n | 0,4,y 1,3,n 1,4,y 2,3,n
|
||||
* 11 0,4,n | 0,4,n 1,4,n 1,4,n 2,4,n
|
||||
*
|
||||
*
|
||||
* [Test fragment was of the form "a\\\"""b c" d.]
|
||||
*
|
||||
* There is very weird mod-3 behaviour going on here in the
|
||||
* number of quotes, and it even applies when there aren't any
|
||||
* backslashes! How ghastly.
|
||||
*
|
||||
* With a bit of thought, this extremely odd diagram suddenly
|
||||
* coalesced itself into a coherent, if still ghastly, model of
|
||||
* how things work:
|
||||
*
|
||||
* - As before, backslashes are only special when one or more
|
||||
* of them appear contiguously before at least one double
|
||||
* quote. In this situation the backslashes do exactly what
|
||||
* you'd expect: each one quotes the next thing in front of
|
||||
* it, so you end up with n/2 literal backslashes (if n is
|
||||
* even) or (n-1)/2 literal backslashes and a literal quote
|
||||
* (if n is odd). In the latter case the double quote
|
||||
* character right after the backslashes is used up.
|
||||
*
|
||||
* - After that, any remaining double quotes are processed. A
|
||||
* string of contiguous unescaped double quotes has a mod-3
|
||||
* behaviour:
|
||||
*
|
||||
* * inside a quoted segment, a quote ends the segment.
|
||||
* * _immediately_ after ending a quoted segment, a quote
|
||||
* simply produces a literal quote.
|
||||
* * otherwise, outside a quoted segment, a quote begins a
|
||||
* quoted segment.
|
||||
*
|
||||
* So, for example, if we started inside a quoted segment
|
||||
* then two contiguous quotes would close the segment and
|
||||
* produce a literal quote; three would close the segment,
|
||||
* produce a literal quote, and open a new segment. If we
|
||||
* started outside a quoted segment, then two contiguous
|
||||
* quotes would open and then close a segment, producing no
|
||||
* output (but potentially creating a zero-length argument);
|
||||
* but three quotes would open and close a segment and then
|
||||
* produce a literal quote.
|
||||
*/
|
||||
|
||||
/*
|
||||
* First deal with the simplest of all special cases: if there
|
||||
* aren't any arguments, return 0,NULL,NULL.
|
||||
*/
|
||||
while (*cmdline && isspace(*cmdline)) cmdline++;
|
||||
if (!*cmdline) {
|
||||
if (argc) *argc = 0;
|
||||
if (argv) *argv = NULL;
|
||||
if (argstart) *argstart = NULL;
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* This will guaranteeably be big enough; we can realloc it
|
||||
* down later.
|
||||
*/
|
||||
outputline = snewn(1+strlen(cmdline), char);
|
||||
outputargv = snewn(strlen(cmdline)+1 / 2, char *);
|
||||
outputargstart = snewn(strlen(cmdline)+1 / 2, char *);
|
||||
|
||||
p = cmdline; q = outputline; outputargc = 0;
|
||||
|
||||
while (*p) {
|
||||
bool quote;
|
||||
|
||||
/* Skip whitespace searching for start of argument. */
|
||||
while (*p && isspace(*p)) p++;
|
||||
if (!*p) break;
|
||||
|
||||
/* We have an argument; start it. */
|
||||
outputargv[outputargc] = q;
|
||||
outputargstart[outputargc] = p;
|
||||
outputargc++;
|
||||
quote = false;
|
||||
|
||||
/* Copy data into the argument until it's finished. */
|
||||
while (*p) {
|
||||
if (!quote && isspace(*p))
|
||||
break; /* argument is finished */
|
||||
|
||||
if (*p == '"' || *p == '\\') {
|
||||
/*
|
||||
* We have a sequence of zero or more backslashes
|
||||
* followed by a sequence of zero or more quotes.
|
||||
* Count up how many of each, and then deal with
|
||||
* them as appropriate.
|
||||
*/
|
||||
int i, slashes = 0, quotes = 0;
|
||||
while (*p == '\\') slashes++, p++;
|
||||
while (*p == '"') quotes++, p++;
|
||||
|
||||
if (!quotes) {
|
||||
/*
|
||||
* Special case: if there are no quotes,
|
||||
* slashes are not special at all, so just copy
|
||||
* n slashes to the output string.
|
||||
*/
|
||||
while (slashes--) *q++ = '\\';
|
||||
} else {
|
||||
/* Slashes annihilate in pairs. */
|
||||
while (slashes >= 2) slashes -= 2, *q++ = '\\';
|
||||
|
||||
/* One remaining slash takes out the first quote. */
|
||||
if (slashes) quotes--, *q++ = '"';
|
||||
|
||||
if (quotes > 0) {
|
||||
/* Outside a quote segment, a quote starts one. */
|
||||
if (!quote) quotes--;
|
||||
|
||||
/* Now we produce (n+1)/3 literal quotes... */
|
||||
for (i = 3; i <= quotes+1; i += 3) *q++ = '"';
|
||||
|
||||
/* ... and end in a quote segment iff 3 divides n. */
|
||||
quote = (quotes % 3 == 0);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
*q++ = *p++;
|
||||
}
|
||||
}
|
||||
|
||||
/* At the end of an argument, just append a trailing NUL. */
|
||||
*q++ = '\0';
|
||||
}
|
||||
|
||||
outputargv = sresize(outputargv, outputargc, char *);
|
||||
outputargstart = sresize(outputargstart, outputargc, char *);
|
||||
|
||||
if (argc) *argc = outputargc;
|
||||
if (argv) *argv = outputargv; else sfree(outputargv);
|
||||
if (argstart) *argstart = outputargstart; else sfree(outputargstart);
|
||||
}
|
||||
|
||||
#ifdef TESTMODE
|
||||
|
||||
const struct argv_test {
|
||||
const char *cmdline;
|
||||
const char *argv[10];
|
||||
} argv_tests[] = {
|
||||
/*
|
||||
* We generate this set of tests by invoking ourself with
|
||||
* `-generate'.
|
||||
*/
|
||||
{"ab c\" d", {"ab", "c d", NULL}},
|
||||
{"a\"b c\" d", {"ab c", "d", NULL}},
|
||||
{"a\"\"b c\" d", {"ab", "c d", NULL}},
|
||||
{"a\"\"\"b c\" d", {"a\"b", "c d", NULL}},
|
||||
{"a\"\"\"\"b c\" d", {"a\"b c", "d", NULL}},
|
||||
{"a\"\"\"\"\"b c\" d", {"a\"b", "c d", NULL}},
|
||||
{"a\"\"\"\"\"\"b c\" d", {"a\"\"b", "c d", NULL}},
|
||||
{"a\"\"\"\"\"\"\"b c\" d", {"a\"\"b c", "d", NULL}},
|
||||
{"a\"\"\"\"\"\"\"\"b c\" d", {"a\"\"b", "c d", NULL}},
|
||||
{"a\\b c\" d", {"a\\b", "c d", NULL}},
|
||||
{"a\\\"b c\" d", {"a\"b", "c d", NULL}},
|
||||
{"a\\\"\"b c\" d", {"a\"b c", "d", NULL}},
|
||||
{"a\\\"\"\"b c\" d", {"a\"b", "c d", NULL}},
|
||||
{"a\\\"\"\"\"b c\" d", {"a\"\"b", "c d", NULL}},
|
||||
{"a\\\"\"\"\"\"b c\" d", {"a\"\"b c", "d", NULL}},
|
||||
{"a\\\"\"\"\"\"\"b c\" d", {"a\"\"b", "c d", NULL}},
|
||||
{"a\\\"\"\"\"\"\"\"b c\" d", {"a\"\"\"b", "c d", NULL}},
|
||||
{"a\\\"\"\"\"\"\"\"\"b c\" d", {"a\"\"\"b c", "d", NULL}},
|
||||
{"a\\\\b c\" d", {"a\\\\b", "c d", NULL}},
|
||||
{"a\\\\\"b c\" d", {"a\\b c", "d", NULL}},
|
||||
{"a\\\\\"\"b c\" d", {"a\\b", "c d", NULL}},
|
||||
{"a\\\\\"\"\"b c\" d", {"a\\\"b", "c d", NULL}},
|
||||
{"a\\\\\"\"\"\"b c\" d", {"a\\\"b c", "d", NULL}},
|
||||
{"a\\\\\"\"\"\"\"b c\" d", {"a\\\"b", "c d", NULL}},
|
||||
{"a\\\\\"\"\"\"\"\"b c\" d", {"a\\\"\"b", "c d", NULL}},
|
||||
{"a\\\\\"\"\"\"\"\"\"b c\" d", {"a\\\"\"b c", "d", NULL}},
|
||||
{"a\\\\\"\"\"\"\"\"\"\"b c\" d", {"a\\\"\"b", "c d", NULL}},
|
||||
{"a\\\\\\b c\" d", {"a\\\\\\b", "c d", NULL}},
|
||||
{"a\\\\\\\"b c\" d", {"a\\\"b", "c d", NULL}},
|
||||
{"a\\\\\\\"\"b c\" d", {"a\\\"b c", "d", NULL}},
|
||||
{"a\\\\\\\"\"\"b c\" d", {"a\\\"b", "c d", NULL}},
|
||||
{"a\\\\\\\"\"\"\"b c\" d", {"a\\\"\"b", "c d", NULL}},
|
||||
{"a\\\\\\\"\"\"\"\"b c\" d", {"a\\\"\"b c", "d", NULL}},
|
||||
{"a\\\\\\\"\"\"\"\"\"b c\" d", {"a\\\"\"b", "c d", NULL}},
|
||||
{"a\\\\\\\"\"\"\"\"\"\"b c\" d", {"a\\\"\"\"b", "c d", NULL}},
|
||||
{"a\\\\\\\"\"\"\"\"\"\"\"b c\" d", {"a\\\"\"\"b c", "d", NULL}},
|
||||
{"a\\\\\\\\b c\" d", {"a\\\\\\\\b", "c d", NULL}},
|
||||
{"a\\\\\\\\\"b c\" d", {"a\\\\b c", "d", NULL}},
|
||||
{"a\\\\\\\\\"\"b c\" d", {"a\\\\b", "c d", NULL}},
|
||||
{"a\\\\\\\\\"\"\"b c\" d", {"a\\\\\"b", "c d", NULL}},
|
||||
{"a\\\\\\\\\"\"\"\"b c\" d", {"a\\\\\"b c", "d", NULL}},
|
||||
{"a\\\\\\\\\"\"\"\"\"b c\" d", {"a\\\\\"b", "c d", NULL}},
|
||||
{"a\\\\\\\\\"\"\"\"\"\"b c\" d", {"a\\\\\"\"b", "c d", NULL}},
|
||||
{"a\\\\\\\\\"\"\"\"\"\"\"b c\" d", {"a\\\\\"\"b c", "d", NULL}},
|
||||
{"a\\\\\\\\\"\"\"\"\"\"\"\"b c\" d", {"a\\\\\"\"b", "c d", NULL}},
|
||||
{"\"ab c\" d", {"ab c", "d", NULL}},
|
||||
{"\"a\"b c\" d", {"ab", "c d", NULL}},
|
||||
{"\"a\"\"b c\" d", {"a\"b", "c d", NULL}},
|
||||
{"\"a\"\"\"b c\" d", {"a\"b c", "d", NULL}},
|
||||
{"\"a\"\"\"\"b c\" d", {"a\"b", "c d", NULL}},
|
||||
{"\"a\"\"\"\"\"b c\" d", {"a\"\"b", "c d", NULL}},
|
||||
{"\"a\"\"\"\"\"\"b c\" d", {"a\"\"b c", "d", NULL}},
|
||||
{"\"a\"\"\"\"\"\"\"b c\" d", {"a\"\"b", "c d", NULL}},
|
||||
{"\"a\"\"\"\"\"\"\"\"b c\" d", {"a\"\"\"b", "c d", NULL}},
|
||||
{"\"a\\b c\" d", {"a\\b c", "d", NULL}},
|
||||
{"\"a\\\"b c\" d", {"a\"b c", "d", NULL}},
|
||||
{"\"a\\\"\"b c\" d", {"a\"b", "c d", NULL}},
|
||||
{"\"a\\\"\"\"b c\" d", {"a\"\"b", "c d", NULL}},
|
||||
{"\"a\\\"\"\"\"b c\" d", {"a\"\"b c", "d", NULL}},
|
||||
{"\"a\\\"\"\"\"\"b c\" d", {"a\"\"b", "c d", NULL}},
|
||||
{"\"a\\\"\"\"\"\"\"b c\" d", {"a\"\"\"b", "c d", NULL}},
|
||||
{"\"a\\\"\"\"\"\"\"\"b c\" d", {"a\"\"\"b c", "d", NULL}},
|
||||
{"\"a\\\"\"\"\"\"\"\"\"b c\" d", {"a\"\"\"b", "c d", NULL}},
|
||||
{"\"a\\\\b c\" d", {"a\\\\b c", "d", NULL}},
|
||||
{"\"a\\\\\"b c\" d", {"a\\b", "c d", NULL}},
|
||||
{"\"a\\\\\"\"b c\" d", {"a\\\"b", "c d", NULL}},
|
||||
{"\"a\\\\\"\"\"b c\" d", {"a\\\"b c", "d", NULL}},
|
||||
{"\"a\\\\\"\"\"\"b c\" d", {"a\\\"b", "c d", NULL}},
|
||||
{"\"a\\\\\"\"\"\"\"b c\" d", {"a\\\"\"b", "c d", NULL}},
|
||||
{"\"a\\\\\"\"\"\"\"\"b c\" d", {"a\\\"\"b c", "d", NULL}},
|
||||
{"\"a\\\\\"\"\"\"\"\"\"b c\" d", {"a\\\"\"b", "c d", NULL}},
|
||||
{"\"a\\\\\"\"\"\"\"\"\"\"b c\" d", {"a\\\"\"\"b", "c d", NULL}},
|
||||
{"\"a\\\\\\b c\" d", {"a\\\\\\b c", "d", NULL}},
|
||||
{"\"a\\\\\\\"b c\" d", {"a\\\"b c", "d", NULL}},
|
||||
{"\"a\\\\\\\"\"b c\" d", {"a\\\"b", "c d", NULL}},
|
||||
{"\"a\\\\\\\"\"\"b c\" d", {"a\\\"\"b", "c d", NULL}},
|
||||
{"\"a\\\\\\\"\"\"\"b c\" d", {"a\\\"\"b c", "d", NULL}},
|
||||
{"\"a\\\\\\\"\"\"\"\"b c\" d", {"a\\\"\"b", "c d", NULL}},
|
||||
{"\"a\\\\\\\"\"\"\"\"\"b c\" d", {"a\\\"\"\"b", "c d", NULL}},
|
||||
{"\"a\\\\\\\"\"\"\"\"\"\"b c\" d", {"a\\\"\"\"b c", "d", NULL}},
|
||||
{"\"a\\\\\\\"\"\"\"\"\"\"\"b c\" d", {"a\\\"\"\"b", "c d", NULL}},
|
||||
{"\"a\\\\\\\\b c\" d", {"a\\\\\\\\b c", "d", NULL}},
|
||||
{"\"a\\\\\\\\\"b c\" d", {"a\\\\b", "c d", NULL}},
|
||||
{"\"a\\\\\\\\\"\"b c\" d", {"a\\\\\"b", "c d", NULL}},
|
||||
{"\"a\\\\\\\\\"\"\"b c\" d", {"a\\\\\"b c", "d", NULL}},
|
||||
{"\"a\\\\\\\\\"\"\"\"b c\" d", {"a\\\\\"b", "c d", NULL}},
|
||||
{"\"a\\\\\\\\\"\"\"\"\"b c\" d", {"a\\\\\"\"b", "c d", NULL}},
|
||||
{"\"a\\\\\\\\\"\"\"\"\"\"b c\" d", {"a\\\\\"\"b c", "d", NULL}},
|
||||
{"\"a\\\\\\\\\"\"\"\"\"\"\"b c\" d", {"a\\\\\"\"b", "c d", NULL}},
|
||||
{"\"a\\\\\\\\\"\"\"\"\"\"\"\"b c\" d", {"a\\\\\"\"\"b", "c d", NULL}},
|
||||
};
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
int i, j;
|
||||
|
||||
if (argc > 1) {
|
||||
/*
|
||||
* Generation of tests.
|
||||
*
|
||||
* Given `-splat <args>', we print out a C-style
|
||||
* representation of each argument (in the form "a", "b",
|
||||
* NULL), backslash-escaping each backslash and double
|
||||
* quote.
|
||||
*
|
||||
* Given `-split <string>', we first doctor `string' by
|
||||
* turning forward slashes into backslashes, single quotes
|
||||
* into double quotes and underscores into spaces; and then
|
||||
* we feed the resulting string to ourself with `-splat'.
|
||||
*
|
||||
* Given `-generate', we concoct a variety of fun test
|
||||
* cases, encode them in quote-safe form (mapping \, " and
|
||||
* space to /, ' and _ respectively) and feed each one to
|
||||
* `-split'.
|
||||
*/
|
||||
if (!strcmp(argv[1], "-splat")) {
|
||||
int i;
|
||||
char *p;
|
||||
for (i = 2; i < argc; i++) {
|
||||
putchar('"');
|
||||
for (p = argv[i]; *p; p++) {
|
||||
if (*p == '\\' || *p == '"')
|
||||
putchar('\\');
|
||||
putchar(*p);
|
||||
}
|
||||
printf("\", ");
|
||||
}
|
||||
printf("NULL");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!strcmp(argv[1], "-split") && argc > 2) {
|
||||
char *str = malloc(20 + strlen(argv[0]) + strlen(argv[2]));
|
||||
char *p, *q;
|
||||
|
||||
q = str + sprintf(str, "%s -splat ", argv[0]);
|
||||
printf(" {\"");
|
||||
for (p = argv[2]; *p; p++, q++) {
|
||||
switch (*p) {
|
||||
case '/': printf("\\\\"); *q = '\\'; break;
|
||||
case '\'': printf("\\\""); *q = '"'; break;
|
||||
case '_': printf(" "); *q = ' '; break;
|
||||
default: putchar(*p); *q = *p; break;
|
||||
}
|
||||
}
|
||||
*p = '\0';
|
||||
printf("\", {");
|
||||
fflush(stdout);
|
||||
|
||||
system(str);
|
||||
|
||||
printf("}},\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!strcmp(argv[1], "-generate")) {
|
||||
char *teststr, *p;
|
||||
int i, initialquote, backslashes, quotes;
|
||||
|
||||
teststr = malloc(200 + strlen(argv[0]));
|
||||
|
||||
for (initialquote = 0; initialquote <= 1; initialquote++) {
|
||||
for (backslashes = 0; backslashes < 5; backslashes++) {
|
||||
for (quotes = 0; quotes < 9; quotes++) {
|
||||
p = teststr + sprintf(teststr, "%s -split ", argv[0]);
|
||||
if (initialquote) *p++ = '\'';
|
||||
*p++ = 'a';
|
||||
for (i = 0; i < backslashes; i++) *p++ = '/';
|
||||
for (i = 0; i < quotes; i++) *p++ = '\'';
|
||||
*p++ = 'b';
|
||||
*p++ = '_';
|
||||
*p++ = 'c';
|
||||
*p++ = '\'';
|
||||
*p++ = '_';
|
||||
*p++ = 'd';
|
||||
*p = '\0';
|
||||
|
||||
system(teststr);
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
fprintf(stderr, "unrecognised option: \"%s\"\n", argv[1]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* If we get here, we were invoked with no arguments, so just
|
||||
* run the tests.
|
||||
*/
|
||||
|
||||
for (i = 0; i < lenof(argv_tests); i++) {
|
||||
int ac;
|
||||
char **av;
|
||||
|
||||
split_into_argv(argv_tests[i].cmdline, &ac, &av);
|
||||
|
||||
for (j = 0; j < ac && argv_tests[i].argv[j]; j++) {
|
||||
if (strcmp(av[j], argv_tests[i].argv[j])) {
|
||||
printf("failed test %d (|%s|) arg %d: |%s| should be |%s|\n",
|
||||
i, argv_tests[i].cmdline,
|
||||
j, av[j], argv_tests[i].argv[j]);
|
||||
}
|
||||
#ifdef VERBOSE
|
||||
else {
|
||||
printf("test %d (|%s|) arg %d: |%s| == |%s|\n",
|
||||
i, argv_tests[i].cmdline,
|
||||
j, av[j], argv_tests[i].argv[j]);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
if (j < ac)
|
||||
printf("failed test %d (|%s|): %d args returned, should be %d\n",
|
||||
i, argv_tests[i].cmdline, ac, j);
|
||||
if (argv_tests[i].argv[j])
|
||||
printf("failed test %d (|%s|): %d args returned, should be more\n",
|
||||
i, argv_tests[i].cmdline, ac);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif
|
12
windows/utils/strtoumax.c
Normal file
12
windows/utils/strtoumax.c
Normal file
@ -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);
|
||||
}
|
40
windows/utils/version.c
Normal file
40
windows/utils/version.c
Normal file
@ -0,0 +1,40 @@
|
||||
#include "putty.h"
|
||||
|
||||
DWORD osMajorVersion, osMinorVersion, osPlatformId;
|
||||
|
||||
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 */
|
||||
}
|
||||
}
|
72
windows/utils/win_strerror.c
Normal file
72
windows/utils/win_strerror.c
Normal file
@ -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;
|
||||
}
|
Reference in New Issue
Block a user