2000-09-14 15:02:50 +00:00
|
|
|
/*
|
|
|
|
* 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>
|
2000-09-14 15:02:50 +00:00
|
|
|
|
2003-04-28 13:59:32 +00:00
|
|
|
#include "putty.h"
|
2017-01-30 19:42:28 +00:00
|
|
|
#include "pageant.h" /* for AGENT_MAX_MSGLEN */
|
2000-12-12 10:33:13 +00:00
|
|
|
|
2021-04-23 05:19:05 +00:00
|
|
|
#include "security-api.h"
|
|
|
|
#include "cryptoapi.h"
|
2010-12-23 15:22:50 +00:00
|
|
|
|
2000-09-19 16:29:28 +00:00
|
|
|
#define AGENT_COPYDATA_ID 0x804e50ba /* random goop */
|
|
|
|
|
2020-01-01 19:28:29 +00:00
|
|
|
static bool wm_copydata_agent_exists(void)
|
2001-05-06 14:35:20 +00:00
|
|
|
{
|
2000-09-14 15:02:50 +00:00
|
|
|
HWND hwnd;
|
|
|
|
hwnd = FindWindow("Pageant", "Pageant");
|
|
|
|
if (!hwnd)
|
2019-09-08 19:29:00 +00:00
|
|
|
return false;
|
2000-09-14 15:02:50 +00:00
|
|
|
else
|
2019-09-08 19:29:00 +00:00
|
|
|
return true;
|
2000-09-14 15:02:50 +00:00
|
|
|
}
|
|
|
|
|
2020-01-01 19:28:29 +00:00
|
|
|
static void wm_copydata_agent_query(strbuf *query, void **out, int *outlen)
|
2001-05-06 14:35:20 +00:00
|
|
|
{
|
2000-09-14 15:02:50 +00:00
|
|
|
HWND hwnd;
|
2003-04-28 13:59:32 +00:00
|
|
|
char *mapname;
|
2000-09-14 15:02:50 +00:00
|
|
|
HANDLE filemap;
|
2000-09-19 16:29:28 +00:00
|
|
|
unsigned char *p, *ret;
|
2000-09-14 15:02:50 +00:00
|
|
|
int id, retlen;
|
|
|
|
COPYDATASTRUCT cds;
|
2010-12-23 15:22:50 +00:00
|
|
|
SECURITY_ATTRIBUTES sa, *psa;
|
|
|
|
PSECURITY_DESCRIPTOR psd = NULL;
|
2011-06-08 20:47:07 +00:00
|
|
|
PSID usersid = NULL;
|
2000-09-14 15:02:50 +00:00
|
|
|
|
|
|
|
*out = NULL;
|
|
|
|
*outlen = 0;
|
|
|
|
|
2018-05-24 12:18:13 +00:00
|
|
|
if (query->len > AGENT_MAX_MSGLEN)
|
2020-01-01 19:28:29 +00:00
|
|
|
return; /* query too large */
|
2018-05-24 12:18:13 +00:00
|
|
|
|
2000-09-14 15:02:50 +00:00
|
|
|
hwnd = FindWindow("Pageant", "Pageant");
|
|
|
|
if (!hwnd)
|
2020-01-01 19:28:29 +00:00
|
|
|
return; /* *out == NULL, so failure */
|
2003-04-28 13:59:32 +00:00
|
|
|
mapname = dupprintf("PageantRequest%08x", (unsigned)GetCurrentThreadId());
|
2010-12-23 15:22:50 +00:00
|
|
|
|
2013-07-21 11:01:22 +00:00
|
|
|
psa = NULL;
|
2013-11-17 14:05:29 +00:00
|
|
|
if (got_advapi()) {
|
2010-12-23 15:22:50 +00:00
|
|
|
/*
|
|
|
|
* 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.
|
|
|
|
*/
|
2011-06-08 20:47:07 +00:00
|
|
|
usersid = get_user_sid();
|
2010-12-23 15:22:50 +00:00
|
|
|
|
2011-06-08 20:47:07 +00:00
|
|
|
if (usersid) {
|
2010-12-23 15:22:50 +00:00
|
|
|
psd = (PSECURITY_DESCRIPTOR)
|
|
|
|
LocalAlloc(LPTR, SECURITY_DESCRIPTOR_MIN_LENGTH);
|
|
|
|
if (psd) {
|
|
|
|
if (p_InitializeSecurityDescriptor
|
|
|
|
(psd, SECURITY_DESCRIPTOR_REVISION) &&
|
2018-10-29 19:50:29 +00:00
|
|
|
p_SetSecurityDescriptorOwner(psd, usersid, false)) {
|
2010-12-23 15:22:50 +00:00
|
|
|
sa.nLength = sizeof(sa);
|
2018-10-29 19:50:29 +00:00
|
|
|
sa.bInheritHandle = true;
|
2010-12-23 15:22:50 +00:00
|
|
|
sa.lpSecurityDescriptor = psd;
|
|
|
|
psa = &sa;
|
|
|
|
} else {
|
|
|
|
LocalFree(psd);
|
|
|
|
psd = NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
filemap = CreateFileMapping(INVALID_HANDLE_VALUE, psa, PAGE_READWRITE,
|
2019-09-08 19:29:00 +00:00
|
|
|
0, AGENT_MAX_MSGLEN, mapname);
|
2013-07-22 07:11:54 +00:00
|
|
|
if (filemap == NULL || filemap == INVALID_HANDLE_VALUE) {
|
|
|
|
sfree(mapname);
|
2020-01-01 19:28:29 +00:00
|
|
|
return; /* *out == NULL, so failure */
|
2013-07-22 07:11:54 +00:00
|
|
|
}
|
2000-09-19 16:29:28 +00:00
|
|
|
p = MapViewOfFile(filemap, FILE_MAP_WRITE, 0, 0, 0);
|
2018-05-24 12:18:13 +00:00
|
|
|
strbuf_finalise_agent_query(query);
|
|
|
|
memcpy(p, query->s, query->len);
|
2000-09-19 16:29:28 +00:00
|
|
|
cds.dwData = AGENT_COPYDATA_ID;
|
2001-05-06 14:35:20 +00:00
|
|
|
cds.cbData = 1 + strlen(mapname);
|
2000-09-19 16:29:28 +00:00
|
|
|
cds.lpData = mapname;
|
2003-04-28 13:59:32 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* 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.
|
|
|
|
*/
|
2005-03-02 15:53:50 +00:00
|
|
|
id = SendMessage(hwnd, WM_COPYDATA, (WPARAM) NULL, (LPARAM) &cds);
|
2000-09-14 15:02:50 +00:00
|
|
|
if (id > 0) {
|
2019-07-06 18:11:56 +00:00
|
|
|
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);
|
2019-09-08 19:29:00 +00:00
|
|
|
memcpy(ret, p, retlen);
|
2019-07-06 18:11:56 +00:00
|
|
|
*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.
|
|
|
|
*/
|
|
|
|
}
|
2000-09-14 15:02:50 +00:00
|
|
|
}
|
2000-09-19 16:29:28 +00:00
|
|
|
UnmapViewOfFile(p);
|
|
|
|
CloseHandle(filemap);
|
2013-07-22 07:11:54 +00:00
|
|
|
sfree(mapname);
|
2010-12-23 15:22:50 +00:00
|
|
|
if (psd)
|
|
|
|
LocalFree(psd);
|
2000-09-14 15:02:50 +00:00
|
|
|
}
|
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
|
|
|
|
2020-01-01 15:55:06 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
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)
|
2020-01-01 16:47:11 +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
|
|
|
char *pipename = agent_named_pipe_name();
|
|
|
|
Socket *s = new_named_pipe_client(pipename, plug);
|
|
|
|
sfree(pipename);
|
|
|
|
return s;
|
2020-01-01 16:47:11 +00:00
|
|
|
}
|
|
|
|
|
2020-01-01 19:28:29 +00:00
|
|
|
static bool named_pipe_agent_exists(void)
|
|
|
|
{
|
|
|
|
char *pipename = agent_named_pipe_name();
|
2021-04-25 05:07:31 +00:00
|
|
|
WIN32_FIND_DATA data;
|
|
|
|
HANDLE ffh = FindFirstFile(pipename, &data);
|
2020-01-01 19:28:29 +00:00
|
|
|
sfree(pipename);
|
2021-04-25 05:07:31 +00:00
|
|
|
if (ffh == INVALID_HANDLE_VALUE)
|
|
|
|
return false;
|
|
|
|
FindClose(ffh);
|
|
|
|
return true;
|
2020-01-01 19:28:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
bool agent_exists(void)
|
|
|
|
{
|
|
|
|
return named_pipe_agent_exists() || wm_copydata_agent_exists();
|
|
|
|
}
|
|
|
|
|
|
|
|
struct agent_pending_query {
|
|
|
|
struct handle *handle;
|
2021-05-13 17:22:05 +00:00
|
|
|
HANDLE os_handle;
|
2020-01-01 19:28:29 +00:00
|
|
|
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);
|
2021-04-09 16:57:10 +00:00
|
|
|
return 0;
|
2020-01-01 19:28:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2020-01-29 06:22:01 +00:00
|
|
|
static agent_pending_query *named_pipe_agent_query(
|
2020-01-01 19:28:29 +00:00
|
|
|
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);
|
2021-05-13 17:22:05 +00:00
|
|
|
pq->os_handle = pipehandle;
|
2021-05-13 17:20:41 +00:00
|
|
|
pipehandle = INVALID_HANDLE_VALUE; /* prevent it being closed below */
|
2020-01-01 19:28:29 +00:00
|
|
|
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);
|
2021-05-13 17:22:05 +00:00
|
|
|
CloseHandle(pq->os_handle);
|
2020-01-01 19:28:29 +00:00
|
|
|
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;
|
|
|
|
}
|