1
0
mirror of https://git.tartarus.org/simon/putty.git synced 2025-01-09 09:27:59 +00:00
putty-source/windows/agent-client.c

294 lines
8.0 KiB
C
Raw Normal View History

/*
* Pageant client code.
*/
#include <stdio.h>
#include <stdlib.h>
Rewrite agent forwarding to serialise requests. The previous agent-forwarding system worked by passing each complete query received from the input to agent_query() as soon as it was ready. So if the remote client were to pipeline multiple requests, then Unix PuTTY (in which agent_query() works asynchronously) would parallelise them into many _simultaneous_ connections to the real agent - and would not track which query went out first, so that if the real agent happened to send its replies (to what _it_ thought were independent clients) in the wrong order, then PuTTY would serialise the replies on to the forwarding channel in whatever order it got them, which wouldn't be the order the remote client was expecting. To solve this, I've done a considerable rewrite, which keeps the request stream in a bufchain, and only removes data from the bufchain when it has a complete request. Then, if agent_query decides to be asynchronous, the forwarding system waits for _that_ agent response before even trying to extract the next request's worth of data from the bufchain. As an added bonus (in principle), this gives agent-forwarding channels some actual flow control for the first time ever! If a client spams us with an endless stream of rapid requests, and never reads its responses, then the output side of the channel will run out of window, which causes us to stop processing requests until we have space to send responses again, which in turn causes us to stop granting extra window on the input side, which serves the client right.
2017-01-29 19:40:38 +00:00
#include <assert.h>
#include "putty.h"
#include "pageant.h" /* for AGENT_MAX_MSGLEN */
#include "security-api.h"
#include "cryptoapi.h"
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);
}
Stream-oriented agent forwarding on Unix. Historically, because of the way Windows Pageant's IPC works, PuTTY's agent forwarding has always been message-oriented. The channel implementation in agentf.c deals with receiving a data stream from the remote agent client and breaking it up into messages, and then it passes each message individually to agent_query(). On Unix, this is more work than is really needed, and I've always meant to get round to doing the more obvious thing: making an agent forwarding channel into simply a stream-oriented proxy, passing raw data back and forth between the SSH channel and the local AF_UNIX socket without having to know or care about the message boundaries in the stream. The portfwdmgr_connect_socket() facility introduced by the previous commit is the missing piece of infrastructure to make that possible. Now, the agent client module provides an API that includes a callback you can pass to portfwdmgr_connect_socket() to open a streamed agent connection, and the agent forwarding setup function tries to use that where possible, only falling back to the message-based agentf.c system if it can't be done. On Windows, the new piece of agent-client API returns failure, so we still fall back to agentf.c there. There are two benefits to doing it this way. One is that it's just simpler and more robust: if PuTTY isn't trying to parse the agent connection, then it has less work to do and fewer places to introduce bugs. The other is that it's futureproof against changes in the agent protocol: if any kind of extension is ever introduced that requires keeping state within a single agent connection, or that changes the protocol itself so that agentf's message-boundary detection stops working, then this forwarding system will still work.
2020-01-01 16:46:44 +00:00
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 09954a87c24e84dac133a9c29ffaef45f145eeca and its followup fix 12aa06ccc98cf8a912eb2ea54f02d234f2f8c173, and simplifies the agent_connect system down to a single trivial function that makes a Socket given a Plug.
2020-01-27 19:34:15 +00:00
Socket *agent_connect(Plug *plug)
{
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 09954a87c24e84dac133a9c29ffaef45f145eeca and its followup fix 12aa06ccc98cf8a912eb2ea54f02d234f2f8c173, and simplifies the agent_connect system down to a single trivial function that makes a Socket given a Plug.
2020-01-27 19:34:15 +00:00
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();
WIN32_FIND_DATA data;
HANDLE ffh = FindFirstFile(pipename, &data);
sfree(pipename);
if (ffh == INVALID_HANDLE_VALUE)
return false;
FindClose(ffh);
return true;
}
bool agent_exists(void)
{
return named_pipe_agent_exists() || wm_copydata_agent_exists();
}
struct agent_pending_query {
struct handle *handle;
HANDLE os_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);
pq->os_handle = pipehandle;
pipehandle = INVALID_HANDLE_VALUE; /* 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);
CloseHandle(pq->os_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;
}