Cross-platform support for speaking SSH agent protocol on a Socket.

The exact nature of the Socket is left up to the front end to decide,
so that we can use a Unix-domain socket on Unix and a Windows named
pipe on Windows. But the logic of how we receive data and what we send
in response is all cross-platform.
This commit is contained in:
Simon Tatham 2015-05-05 20:16:20 +01:00
Родитель 5ba2d611f9
Коммит 7b6078533e
2 изменённых файлов: 196 добавлений и 0 удалений

183
pageant.c
Просмотреть файл

@ -765,3 +765,186 @@ int pageant_delete_ssh2_key(struct ssh2_userkey *skey)
assert(deleted == skey); assert(deleted == skey);
return TRUE; return TRUE;
} }
/* ----------------------------------------------------------------------
* The agent plug.
*/
/*
* Coroutine macros similar to, but simplified from, those in ssh.c.
*/
#define crBegin(v) { int *crLine = &v; switch(v) { case 0:;
#define crFinish(z) } *crLine = 0; return (z); }
#define crGetChar(c) do \
{ \
while (len == 0) { \
*crLine =__LINE__; return 1; case __LINE__:; \
} \
len--; \
(c) = (unsigned char)*data++; \
} while (0)
struct pageant_conn_state {
const struct plug_function_table *fn;
/* the above variable absolutely *must* be the first in this structure */
Socket connsock;
void *logctx;
void (*logfn)(void *logctx, const char *fmt, ...);
unsigned char lenbuf[4], pktbuf[AGENT_MAX_MSGLEN];
unsigned len, got;
int real_packet;
int crLine; /* for coroutine in pageant_conn_receive */
};
static int pageant_conn_closing(Plug plug, const char *error_msg,
int error_code, int calling_back)
{
struct pageant_conn_state *pc = (struct pageant_conn_state *)plug;
if (error_msg && pc->logfn)
pc->logfn(pc->logctx, "Pageant connection socket: %s", error_msg);
sk_close(pc->connsock);
sfree(pc);
return 1;
}
static void pageant_conn_sent(Plug plug, int bufsize)
{
/* struct pageant_conn_state *pc = (struct pageant_conn_state *)plug; */
/*
* We do nothing here, because we expect that there won't be a
* need to throttle and unthrottle the connection to an agent -
* clients will typically not send many requests, and will wait
* until they receive each reply before sending a new request.
*/
}
static int pageant_conn_receive(Plug plug, int urgent, char *data, int len)
{
struct pageant_conn_state *pc = (struct pageant_conn_state *)plug;
char c;
crBegin(pc->crLine);
while (len > 0) {
pc->got = 0;
while (pc->got < 4) {
crGetChar(c);
pc->lenbuf[pc->got++] = c;
}
pc->len = GET_32BIT(pc->lenbuf);
pc->got = 0;
pc->real_packet = (pc->len < AGENT_MAX_MSGLEN-4);
while (pc->got < pc->len) {
crGetChar(c);
if (pc->real_packet)
pc->pktbuf[pc->got] = c;
pc->got++;
}
{
void *reply;
int replylen;
if (pc->real_packet) {
reply = pageant_handle_msg(pc->pktbuf, pc->len, &replylen);
} else {
reply = pageant_failure_msg(&replylen);
}
sk_write(pc->connsock, reply, replylen);
smemclr(reply, replylen);
}
}
crFinish(1);
}
struct pageant_listen_state {
const struct plug_function_table *fn;
/* the above variable absolutely *must* be the first in this structure */
Socket listensock;
void *logctx;
void (*logfn)(void *logctx, const char *fmt, ...);
};
static int pageant_listen_closing(Plug plug, const char *error_msg,
int error_code, int calling_back)
{
struct pageant_listen_state *pl = (struct pageant_listen_state *)plug;
if (error_msg && pl->logfn)
pl->logfn(pl->logctx, "Pageant listening socket: %s", error_msg);
sk_close(pl->listensock);
pl->listensock = NULL;
return 1;
}
static int pageant_listen_accepting(Plug plug,
accept_fn_t constructor, accept_ctx_t ctx)
{
static const struct plug_function_table connection_fn_table = {
NULL, /* no log function, because that's for outgoing connections */
pageant_conn_closing,
pageant_conn_receive,
pageant_conn_sent,
NULL /* no accepting function, because we've already done it */
};
struct pageant_listen_state *pl = (struct pageant_listen_state *)plug;
struct pageant_conn_state *pc;
const char *err;
pc = snew(struct pageant_conn_state);
pc->fn = &connection_fn_table;
pc->logfn = pl->logfn;
pc->logctx = pl->logctx;
pc->crLine = 0;
pc->connsock = constructor(ctx, (Plug) pc);
if ((err = sk_socket_error(pc->connsock)) != NULL) {
sk_close(pc->connsock);
sfree(pc);
return TRUE;
}
sk_set_frozen(pc->connsock, 0);
/* FIXME: can we get any useful peer id info? */
if (pl->logfn)
pl->logfn(pl->logctx, "Pageant socket connected");
return 0;
}
struct pageant_listen_state *pageant_listener_new
(void *logctx, void (*logfn)(void *logctx, const char *fmt, ...))
{
static const struct plug_function_table listener_fn_table = {
NULL, /* no log function, because that's for outgoing connections */
pageant_listen_closing,
NULL, /* no receive function on a listening socket */
NULL, /* no sent function on a listening socket */
pageant_listen_accepting
};
struct pageant_listen_state *pl = snew(struct pageant_listen_state);
pl->fn = &listener_fn_table;
pl->logctx = logctx;
pl->logfn = logfn;
pl->listensock = NULL;
return pl;
}
void pageant_listener_got_socket(struct pageant_listen_state *pl, Socket sock)
{
pl->listensock = sock;
}
void pageant_listener_free(struct pageant_listen_state *pl)
{
if (pl->listensock)
sk_close(pl->listensock);
sfree(pl);
}

Просмотреть файл

@ -65,3 +65,16 @@ int pageant_delete_ssh2_key(struct ssh2_userkey *skey);
* empty. * empty.
*/ */
void keylist_update(void); void keylist_update(void);
/*
* Functions to establish a listening socket speaking the SSH agent
* protocol. Call pageant_listener_new() to set up a state; then
* create a socket using the returned pointer as a Plug; then call
* pageant_listener_got_socket() to give the listening state its own
* socket pointer.
*/
struct pageant_listen_state;
struct pageant_listen_state *pageant_listener_new
(void *logctx, void (*logfn)(void *logctx, const char *fmt, ...));
void pageant_listener_got_socket(struct pageant_listen_state *pl, Socket sock);
void pageant_listener_free(struct pageant_listen_state *pl);