зеркало из https://github.com/github/putty.git
470 строки
13 KiB
C
470 строки
13 KiB
C
/*
|
|
* 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) || defined COVERITY
|
|
/* For older Visual Studio, and also for the system I
|
|
* currently use for Coveritying the Windows code, 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, NULL);
|
|
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;
|
|
}
|