#include <unistd.h>
#include <sys/socket.h>
#include <sys/un.h>
+#include <fcntl.h>
#include "putty.h"
#include "misc.h"
int agent_exists(void)
{
- if (getenv("SSH_AUTH_SOCK") != NULL)
+ const char *p = getenv("SSH_AUTH_SOCK");
+ if (p && *p)
return TRUE;
return FALSE;
}
-static tree234 *agent_connections;
-struct agent_connection {
+static tree234 *agent_pending_queries;
+struct agent_pending_query {
int fd;
char *retbuf;
char sizebuf[4];
};
static int agent_conncmp(void *av, void *bv)
{
- struct agent_connection *a = (struct agent_connection *) av;
- struct agent_connection *b = (struct agent_connection *) bv;
+ agent_pending_query *a = (agent_pending_query *) av;
+ agent_pending_query *b = (agent_pending_query *) bv;
if (a->fd < b->fd)
return -1;
if (a->fd > b->fd)
static int agent_connfind(void *av, void *bv)
{
int afd = *(int *) av;
- struct agent_connection *b = (struct agent_connection *) bv;
+ agent_pending_query *b = (agent_pending_query *) bv;
if (afd < b->fd)
return -1;
if (afd > b->fd)
return 0;
}
-static int agent_select_result(int fd, int event)
+/*
+ * Attempt to read from an agent socket fd. Returns 0 if the expected
+ * response is as yet incomplete; returns 1 if it's either complete
+ * (conn->retbuf non-NULL and filled with something useful) or has
+ * failed totally (conn->retbuf is NULL).
+ */
+static int agent_try_read(agent_pending_query *conn)
{
int ret;
- struct agent_connection *conn;
-
- assert(event == 1); /* not selecting for anything but R */
-
- conn = find234(agent_connections, &fd, agent_connfind);
- if (!conn) {
- uxsel_del(fd);
- return 1;
- }
- ret = read(fd, conn->retbuf+conn->retlen, conn->retsize-conn->retlen);
+ ret = read(conn->fd, conn->retbuf+conn->retlen,
+ conn->retsize-conn->retlen);
if (ret <= 0) {
if (conn->retbuf != conn->sizebuf) sfree(conn->retbuf);
conn->retbuf = NULL;
conn->retlen = 0;
- goto done;
+ return 1;
}
conn->retlen += ret;
if (conn->retsize == 4 && conn->retlen == 4) {
- conn->retsize = GET_32BIT(conn->retbuf);
+ conn->retsize = toint(GET_32BIT(conn->retbuf) + 4);
if (conn->retsize <= 0) {
conn->retbuf = NULL;
conn->retlen = 0;
- goto done;
+ return -1; /* way too large */
}
- conn->retsize += 4;
assert(conn->retbuf == conn->sizebuf);
conn->retbuf = snewn(conn->retsize, char);
memcpy(conn->retbuf, conn->sizebuf, 4);
if (conn->retlen < conn->retsize)
return 0; /* more data to come */
- done:
+ return 1;
+}
+
+void agent_cancel_query(agent_pending_query *conn)
+{
+ uxsel_del(conn->fd);
+ close(conn->fd);
+ del234(agent_pending_queries, conn);
+ sfree(conn);
+}
+
+static int agent_select_result(int fd, int event)
+{
+ agent_pending_query *conn;
+
+ assert(event == 1); /* not selecting for anything but R */
+
+ conn = find234(agent_pending_queries, &fd, agent_connfind);
+ if (!conn) {
+ uxsel_del(fd);
+ return 1;
+ }
+
+ if (!agent_try_read(conn))
+ return 0; /* more data to come */
+
/*
* We have now completed the agent query. Do the callback, and
* clean up. (Of course we don't free retbuf, since ownership
* of that passes to the callback.)
*/
conn->callback(conn->callback_ctx, conn->retbuf, conn->retlen);
- uxsel_del(fd);
- close(fd);
- del234(agent_connections, conn);
- sfree(conn);
+ agent_cancel_query(conn);
return 0;
}
-int agent_query(void *in, int inlen, void **out, int *outlen,
- void (*callback)(void *, void *, int), void *callback_ctx)
+agent_pending_query *agent_query(
+ void *in, int inlen, void **out, int *outlen,
+ void (*callback)(void *, void *, int), void *callback_ctx)
{
char *name;
int sock;
struct sockaddr_un addr;
int done;
- struct agent_connection *conn;
+ agent_pending_query *conn;
name = getenv("SSH_AUTH_SOCK");
if (!name)
exit(1);
}
+ cloexec(sock);
+
addr.sun_family = AF_UNIX;
strncpy(addr.sun_path, name, sizeof(addr.sun_path));
if (connect(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
done += ret;
}
- if (!agent_connections)
- agent_connections = newtree234(agent_conncmp);
-
- conn = snew(struct agent_connection);
+ conn = snew(agent_pending_query);
conn->fd = sock;
conn->retbuf = conn->sizebuf;
conn->retsize = 4;
conn->retlen = 0;
conn->callback = callback;
conn->callback_ctx = callback_ctx;
- add234(agent_connections, conn);
+
+ if (!callback) {
+ /*
+ * Bodge to permit making deliberately synchronous agent
+ * requests. Used by Unix Pageant in command-line client mode,
+ * which is legit because it really is true that no other part
+ * of the program is trying to get anything useful done
+ * simultaneously. But this special case shouldn't be used in
+ * any more general program.
+ */
+ no_nonblock(conn->fd);
+ while (!agent_try_read(conn))
+ /* empty loop body */;
+
+ *out = conn->retbuf;
+ *outlen = conn->retlen;
+ sfree(conn);
+ return NULL;
+ }
+
+ /*
+ * Otherwise do it properly: add conn to the tree of agent
+ * connections currently in flight, return 0 to indicate that the
+ * response hasn't been received yet, and call the callback when
+ * select_result comes back to us.
+ */
+ if (!agent_pending_queries)
+ agent_pending_queries = newtree234(agent_conncmp);
+ add234(agent_pending_queries, conn);
uxsel_set(sock, 1, agent_select_result);
- return 0;
+ return conn;
failure:
*out = NULL;
*outlen = 0;
- return 1;
+ return NULL;
}