1
0
mirror of https://git.tartarus.org/simon/putty.git synced 2025-01-10 01:48:00 +00:00
putty-source/windows/winpgntc.c
Simon Tatham 2160205aee Merge the two low-level portfwd setup systems.
In commit 09954a87c I introduced the portfwdmgr_connect_socket()
system, which opened a port forwarding given a callback to create the
Socket itself, with the aim of using it to make forwardings to Unix-
domain sockets and Windows named pipes (both initially for agent
forwarding).

But I forgot that a year and a bit ago, in commit 834396170, I already
introduced a similar low-level system for creating a PortForwarding
around an unusual kind of socket: the portfwd_raw_new() system, which
in place of a callback uses a two-phase setup protocol (you create the
socket in between the two setup calls, and can roll it back if the
socket can't be created).

There's really no need to have _both_ these systems! So now I'm
merging them, which is to say, I'm enhancing portfwd_raw_new to have
the one new feature it needs, and throwing away the newer system
completely. The new feature is to be able to control the initial state
of the 'ready' flag: portfwd_raw_new was always used for initiating
port forwardings in response to an incoming local connection, which
means you need to start off with ready=false and set it true when the
other end of the SSH connection sends back OPEN_CONFIRMATION. Now it's
being used for initiating port forwardings in response to a
CHANNEL_OPEN, we need to be able to start with ready=true.

This commit reverts 09954a87c2 and its
followup fix 12aa06ccc9, and simplifies
the agent_connect system down to a single trivial function that makes
a Socket given a Plug.
2020-01-27 19:40:50 +00:00

341 lines
8.9 KiB
C

/*
* Pageant client code.
*/
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include "putty.h"
#include "pageant.h" /* for AGENT_MAX_MSGLEN */
#ifndef NO_SECURITY
#include "winsecur.h"
#include "wincapi.h"
#endif
#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;
#ifndef NO_SECURITY
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;
}
}
}
}
#endif /* NO_SECURITY */
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);
}
#ifndef NO_SECURITY
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);
}
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;
}
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;
}
#else /* NO_SECURITY */
Socket *agent_connect(void *vctx, Plug *plug)
{
unreachable("no agent_connect_ctx can be constructed on this platform");
}
agent_connect_ctx *agent_get_connect_ctx(void)
{
return NULL;
}
void agent_free_connect_ctx(agent_connect_ctx *ctx)
{
}
bool agent_exists(void)
{
return wm_copydata_agent_exists();
}
agent_pending_query *agent_query(
strbuf *query, void **out, int *outlen,
void (*callback)(void *, void *, int), void *callback_ctx)
{
wm_copydata_agent_query(query, out, outlen);
return NULL;
}
void agent_cancel_query(agent_pending_query *q)
{
unreachable("Windows agent queries are never asynchronous!");
}
#endif /* NO_SECURITY */