mirror of
https://git.tartarus.org/simon/putty.git
synced 2025-01-10 01:48:00 +00:00
6c783f9ad0
It's had its day. It was there to support pre-WinNT platforms, on which the security APIs don't exist - but more specifically, it was there to support _build tools_ that only knew about pre-WinNT versions of Windows, so that you couldn't even compile a program that would _try_ to refer to the interprocess security APIs. But we don't support those build systems any more in any case: more recent changes like the assumption of (most of) C99 will have stopped this code from building with compilers that old. So there's no reason to clutter the code with backwards compatibility features that won't help. I left NO_SECURITY in place during the CMake migration, so that _just_ in case it needs resurrecting, some version of it will be available in the git history. But I don't expect it to be needed, and I'm deleting the whole thing now. The _runtime_ check for interprocess security libraries is still in place. So PuTTY tools built with a modern toolchain can still at least try to run on the Win95/98/ME series, and they should detect that those system DLLs don't exist and proceed sensibly in their absence. That may also be a thing to throw out sooner or later, but I haven't thrown it out as part of this commit.
300 lines
8.2 KiB
C
300 lines
8.2 KiB
C
/*
|
|
* Pageant client code.
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <assert.h>
|
|
|
|
#include "putty.h"
|
|
#include "pageant.h" /* for AGENT_MAX_MSGLEN */
|
|
|
|
#include "winsecur.h"
|
|
#include "wincapi.h"
|
|
|
|
#define AGENT_COPYDATA_ID 0x804e50ba /* random goop */
|
|
|
|
static bool wm_copydata_agent_exists(void)
|
|
{
|
|
HWND hwnd;
|
|
hwnd = FindWindow("Pageant", "Pageant");
|
|
if (!hwnd)
|
|
return false;
|
|
else
|
|
return true;
|
|
}
|
|
|
|
static void wm_copydata_agent_query(strbuf *query, void **out, int *outlen)
|
|
{
|
|
HWND hwnd;
|
|
char *mapname;
|
|
HANDLE filemap;
|
|
unsigned char *p, *ret;
|
|
int id, retlen;
|
|
COPYDATASTRUCT cds;
|
|
SECURITY_ATTRIBUTES sa, *psa;
|
|
PSECURITY_DESCRIPTOR psd = NULL;
|
|
PSID usersid = NULL;
|
|
|
|
*out = NULL;
|
|
*outlen = 0;
|
|
|
|
if (query->len > AGENT_MAX_MSGLEN)
|
|
return; /* query too large */
|
|
|
|
hwnd = FindWindow("Pageant", "Pageant");
|
|
if (!hwnd)
|
|
return; /* *out == NULL, so failure */
|
|
mapname = dupprintf("PageantRequest%08x", (unsigned)GetCurrentThreadId());
|
|
|
|
psa = NULL;
|
|
if (got_advapi()) {
|
|
/*
|
|
* Make the file mapping we create for communication with
|
|
* Pageant owned by the user SID rather than the default. This
|
|
* should make communication between processes with slightly
|
|
* different contexts more reliable: in particular, command
|
|
* prompts launched as administrator should still be able to
|
|
* run PSFTPs which refer back to the owning user's
|
|
* unprivileged Pageant.
|
|
*/
|
|
usersid = get_user_sid();
|
|
|
|
if (usersid) {
|
|
psd = (PSECURITY_DESCRIPTOR)
|
|
LocalAlloc(LPTR, SECURITY_DESCRIPTOR_MIN_LENGTH);
|
|
if (psd) {
|
|
if (p_InitializeSecurityDescriptor
|
|
(psd, SECURITY_DESCRIPTOR_REVISION) &&
|
|
p_SetSecurityDescriptorOwner(psd, usersid, false)) {
|
|
sa.nLength = sizeof(sa);
|
|
sa.bInheritHandle = true;
|
|
sa.lpSecurityDescriptor = psd;
|
|
psa = &sa;
|
|
} else {
|
|
LocalFree(psd);
|
|
psd = NULL;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
filemap = CreateFileMapping(INVALID_HANDLE_VALUE, psa, PAGE_READWRITE,
|
|
0, AGENT_MAX_MSGLEN, mapname);
|
|
if (filemap == NULL || filemap == INVALID_HANDLE_VALUE) {
|
|
sfree(mapname);
|
|
return; /* *out == NULL, so failure */
|
|
}
|
|
p = MapViewOfFile(filemap, FILE_MAP_WRITE, 0, 0, 0);
|
|
strbuf_finalise_agent_query(query);
|
|
memcpy(p, query->s, query->len);
|
|
cds.dwData = AGENT_COPYDATA_ID;
|
|
cds.cbData = 1 + strlen(mapname);
|
|
cds.lpData = mapname;
|
|
|
|
/*
|
|
* The user either passed a null callback (indicating that the
|
|
* query is required to be synchronous) or CreateThread failed.
|
|
* Either way, we need a synchronous request.
|
|
*/
|
|
id = SendMessage(hwnd, WM_COPYDATA, (WPARAM) NULL, (LPARAM) &cds);
|
|
if (id > 0) {
|
|
uint32_t length_field = GET_32BIT_MSB_FIRST(p);
|
|
if (length_field > 0 && length_field <= AGENT_MAX_MSGLEN - 4) {
|
|
retlen = length_field + 4;
|
|
ret = snewn(retlen, unsigned char);
|
|
memcpy(ret, p, retlen);
|
|
*out = ret;
|
|
*outlen = retlen;
|
|
} else {
|
|
/*
|
|
* If we get here, we received an out-of-range length
|
|
* field, either without space for a message type code or
|
|
* overflowing the FileMapping.
|
|
*
|
|
* Treat this as if Pageant didn't answer at all - which
|
|
* actually means we do nothing, and just don't fill in
|
|
* out and outlen.
|
|
*/
|
|
}
|
|
}
|
|
UnmapViewOfFile(p);
|
|
CloseHandle(filemap);
|
|
sfree(mapname);
|
|
if (psd)
|
|
LocalFree(psd);
|
|
}
|
|
|
|
char *agent_named_pipe_name(void)
|
|
{
|
|
char *username, *suffix, *pipename;
|
|
username = get_username();
|
|
suffix = capi_obfuscate_string("Pageant");
|
|
pipename = dupprintf("\\\\.\\pipe\\pageant.%s.%s", username, suffix);
|
|
sfree(username);
|
|
sfree(suffix);
|
|
return pipename;
|
|
}
|
|
|
|
Socket *agent_connect(Plug *plug)
|
|
{
|
|
char *pipename = agent_named_pipe_name();
|
|
Socket *s = new_named_pipe_client(pipename, plug);
|
|
sfree(pipename);
|
|
return s;
|
|
}
|
|
|
|
static bool named_pipe_agent_exists(void)
|
|
{
|
|
char *pipename = agent_named_pipe_name();
|
|
DWORD type = GetFileType(pipename);
|
|
sfree(pipename);
|
|
return type == FILE_TYPE_PIPE;
|
|
}
|
|
|
|
bool agent_exists(void)
|
|
{
|
|
return named_pipe_agent_exists() || wm_copydata_agent_exists();
|
|
}
|
|
|
|
struct agent_pending_query {
|
|
struct handle *handle;
|
|
strbuf *response;
|
|
void (*callback)(void *, void *, int);
|
|
void *callback_ctx;
|
|
};
|
|
|
|
static int named_pipe_agent_accumulate_response(
|
|
strbuf *sb, const void *data, size_t len)
|
|
{
|
|
put_data(sb, data, len);
|
|
if (sb->len >= 4) {
|
|
uint32_t length_field = GET_32BIT_MSB_FIRST(sb->u);
|
|
if (length_field > AGENT_MAX_MSGLEN)
|
|
return -1; /* badly formatted message */
|
|
|
|
int overall_length = length_field + 4;
|
|
if (sb->len >= overall_length)
|
|
return overall_length;
|
|
}
|
|
|
|
return 0; /* not done yet */
|
|
}
|
|
|
|
static size_t named_pipe_agent_gotdata(
|
|
struct handle *h, const void *data, size_t len, int err)
|
|
{
|
|
agent_pending_query *pq = handle_get_privdata(h);
|
|
|
|
if (err || len == 0) {
|
|
pq->callback(pq->callback_ctx, NULL, 0);
|
|
agent_cancel_query(pq);
|
|
return 0;
|
|
}
|
|
|
|
int status = named_pipe_agent_accumulate_response(pq->response, data, len);
|
|
if (status == -1) {
|
|
pq->callback(pq->callback_ctx, NULL, 0);
|
|
agent_cancel_query(pq);
|
|
} else if (status > 0) {
|
|
void *response_buf = strbuf_to_str(pq->response);
|
|
pq->response = NULL;
|
|
pq->callback(pq->callback_ctx, response_buf, status);
|
|
agent_cancel_query(pq);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static agent_pending_query *named_pipe_agent_query(
|
|
strbuf *query, void **out, int *outlen,
|
|
void (*callback)(void *, void *, int), void *callback_ctx)
|
|
{
|
|
agent_pending_query *pq = NULL;
|
|
char *err = NULL, *pipename = NULL;
|
|
strbuf *sb = NULL;
|
|
HANDLE pipehandle;
|
|
|
|
pipename = agent_named_pipe_name();
|
|
pipehandle = connect_to_named_pipe(pipename, &err);
|
|
if (pipehandle == INVALID_HANDLE_VALUE)
|
|
goto failure;
|
|
|
|
strbuf_finalise_agent_query(query);
|
|
|
|
for (DWORD done = 0; done < query->len ;) {
|
|
DWORD nwritten;
|
|
bool ret = WriteFile(pipehandle, query->s + done, query->len - done,
|
|
&nwritten, NULL);
|
|
if (!ret)
|
|
goto failure;
|
|
|
|
done += nwritten;
|
|
}
|
|
|
|
if (!callback) {
|
|
int status;
|
|
|
|
sb = strbuf_new_nm();
|
|
do {
|
|
char buf[1024];
|
|
DWORD nread;
|
|
bool ret = ReadFile(pipehandle, buf, sizeof(buf), &nread, NULL);
|
|
if (!ret)
|
|
goto failure;
|
|
status = named_pipe_agent_accumulate_response(sb, buf, nread);
|
|
} while (status == 0);
|
|
|
|
if (status == -1)
|
|
goto failure;
|
|
|
|
*out = strbuf_to_str(sb);
|
|
*outlen = status;
|
|
sb = NULL;
|
|
pq = NULL;
|
|
goto out;
|
|
}
|
|
|
|
pq = snew(agent_pending_query);
|
|
pq->handle = handle_input_new(pipehandle, named_pipe_agent_gotdata, pq, 0);
|
|
pipehandle = NULL; /* prevent it being closed below */
|
|
pq->response = strbuf_new_nm();
|
|
pq->callback = callback;
|
|
pq->callback_ctx = callback_ctx;
|
|
goto out;
|
|
|
|
failure:
|
|
*out = NULL;
|
|
*outlen = 0;
|
|
pq = NULL;
|
|
|
|
out:
|
|
sfree(err);
|
|
sfree(pipename);
|
|
if (pipehandle != INVALID_HANDLE_VALUE)
|
|
CloseHandle(pipehandle);
|
|
if (sb)
|
|
strbuf_free(sb);
|
|
return pq;
|
|
}
|
|
|
|
void agent_cancel_query(agent_pending_query *pq)
|
|
{
|
|
handle_free(pq->handle);
|
|
if (pq->response)
|
|
strbuf_free(pq->response);
|
|
sfree(pq);
|
|
}
|
|
|
|
agent_pending_query *agent_query(
|
|
strbuf *query, void **out, int *outlen,
|
|
void (*callback)(void *, void *, int), void *callback_ctx)
|
|
{
|
|
agent_pending_query *pq = named_pipe_agent_query(
|
|
query, out, outlen, callback, callback_ctx);
|
|
if (pq || *out)
|
|
return pq;
|
|
|
|
wm_copydata_agent_query(query, out, outlen);
|
|
return NULL;
|
|
}
|