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
|
|
|
|
2010-12-23 15:22:50 +00:00
|
|
|
#ifndef NO_SECURITY
|
2013-11-17 14:05:29 +00:00
|
|
|
#include "winsecur.h"
|
2020-01-01 15:55:06 +00:00
|
|
|
#include "wincapi.h"
|
2010-12-23 15:22:50 +00:00
|
|
|
#endif
|
|
|
|
|
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;
|
2010-12-23 15:22:50 +00:00
|
|
|
#ifndef NO_SECURITY
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif /* NO_SECURITY */
|
|
|
|
|
|
|
|
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
|
|
|
#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;
|
|
|
|
}
|
|
|
|
|
2020-01-01 16:47:11 +00:00
|
|
|
struct agent_connect_ctx {
|
|
|
|
char *pipename;
|
|
|
|
};
|
|
|
|
|
|
|
|
Socket *agent_connect(void *vctx, Plug *plug)
|
|
|
|
{
|
|
|
|
agent_connect_ctx *ctx = (agent_connect_ctx *)vctx;
|
|
|
|
return new_named_pipe_client(ctx->pipename, plug);
|
|
|
|
}
|
|
|
|
|
|
|
|
agent_connect_ctx *agent_get_connect_ctx(void)
|
|
|
|
{
|
|
|
|
agent_connect_ctx *ctx = snew(agent_connect_ctx);
|
|
|
|
ctx->pipename = agent_named_pipe_name();
|
|
|
|
return ctx;
|
|
|
|
}
|
|
|
|
|
|
|
|
void agent_free_connect_ctx(agent_connect_ctx *ctx)
|
|
|
|
{
|
|
|
|
sfree(ctx->pipename);
|
|
|
|
sfree(ctx);
|
|
|
|
}
|
|
|
|
|
2020-01-01 19:28:29 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2020-01-01 16:47:11 +00:00
|
|
|
#else /* NO_SECURITY */
|
2020-01-01 15:55:06 +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
|
|
|
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)
|
|
|
|
{
|
|
|
|
}
|
2020-01-01 16:47:11 +00:00
|
|
|
|
2020-01-01 19:28:29 +00:00
|
|
|
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!");
|
|
|
|
}
|
|
|
|
|
2020-01-01 16:47:11 +00:00
|
|
|
#endif /* NO_SECURITY */
|