123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227 |
- /*
- * SSH agent client code.
- */
- #include <stdio.h>
- #include <stdlib.h>
- #include <assert.h>
- #include <unistd.h>
- #include <sys/socket.h>
- #include <sys/un.h>
- #include <fcntl.h>
- #include "putty.h"
- #include "misc.h"
- #include "tree234.h"
- #include "puttymem.h"
- bool agent_exists(void)
- {
- const char *p = getenv("SSH_AUTH_SOCK");
- if (p && *p)
- return true;
- return false;
- }
- static tree234 *agent_pending_queries;
- struct agent_pending_query {
- int fd;
- char *retbuf;
- char sizebuf[4];
- int retsize, retlen;
- void (*callback)(void *, void *, int);
- void *callback_ctx;
- };
- static int agent_conncmp(void *av, void *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)
- return +1;
- return 0;
- }
- static int agent_connfind(void *av, void *bv)
- {
- int afd = *(int *) av;
- agent_pending_query *b = (agent_pending_query *) bv;
- if (afd < b->fd)
- return -1;
- if (afd > b->fd)
- return +1;
- return 0;
- }
- /*
- * Attempt to read from an agent socket fd. Returns false if the
- * expected response is as yet incomplete; returns true if it's either
- * complete (conn->retbuf non-NULL and filled with something useful)
- * or has failed totally (conn->retbuf is NULL).
- */
- static bool agent_try_read(agent_pending_query *conn)
- {
- int ret;
- 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;
- return true;
- }
- conn->retlen += ret;
- if (conn->retsize == 4 && conn->retlen == 4) {
- conn->retsize = toint(GET_32BIT_MSB_FIRST(conn->retbuf) + 4);
- if (conn->retsize <= 0) {
- conn->retbuf = NULL;
- conn->retlen = 0;
- return true; /* way too large */
- }
- assert(conn->retbuf == conn->sizebuf);
- conn->retbuf = snewn(conn->retsize, char);
- memcpy(conn->retbuf, conn->sizebuf, 4);
- }
- if (conn->retlen < conn->retsize)
- return false; /* more data to come */
- return true;
- }
- void agent_cancel_query(agent_pending_query *conn)
- {
- uxsel_del(conn->fd);
- close(conn->fd);
- del234(agent_pending_queries, conn);
- if (conn->retbuf && conn->retbuf != conn->sizebuf)
- sfree(conn->retbuf);
- sfree(conn);
- }
- static void agent_select_result(int fd, int event)
- {
- agent_pending_query *conn;
- assert(event == SELECT_R); /* not selecting for anything but R */
- conn = find234(agent_pending_queries, &fd, agent_connfind);
- if (!conn) {
- uxsel_del(fd);
- return;
- }
- if (!agent_try_read(conn))
- return; /* more data to come */
- /*
- * We have now completed the agent query. Do the callback.
- */
- conn->callback(conn->callback_ctx, conn->retbuf, conn->retlen);
- /* Null out conn->retbuf, since ownership of that buffer has
- * passed to the callback. */
- conn->retbuf = NULL;
- agent_cancel_query(conn);
- }
- static const char *agent_socket_path(void)
- {
- return getenv("SSH_AUTH_SOCK");
- }
- Socket *agent_connect(Plug *plug)
- {
- const char *path = agent_socket_path();
- if (!path)
- return new_error_socket_fmt(plug, "SSH_AUTH_SOCK not set");
- return sk_new(unix_sock_addr(path), 0, false, false, false, false, plug);
- }
- agent_pending_query *agent_query(
- strbuf *query, void **out, int *outlen,
- void (*callback)(void *, void *, int), void *callback_ctx)
- {
- const char *name;
- int sock;
- struct sockaddr_un addr;
- int done;
- agent_pending_query *conn;
- name = agent_socket_path();
- if (!name || strlen(name) >= sizeof(addr.sun_path))
- goto failure;
- sock = socket(PF_UNIX, SOCK_STREAM, 0);
- if (sock < 0) {
- perror("socket(PF_UNIX)");
- exit(1);
- }
- cloexec(sock);
- addr.sun_family = AF_UNIX;
- strcpy(addr.sun_path, name);
- if (connect(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
- close(sock);
- goto failure;
- }
- strbuf_finalise_agent_query(query);
- for (done = 0; done < query->len ;) {
- int ret = write(sock, query->s + done,
- query->len - done);
- if (ret <= 0) {
- close(sock);
- goto failure;
- }
- done += ret;
- }
- 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;
- 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, SELECT_R, agent_select_result);
- return conn;
- failure:
- *out = NULL;
- *outlen = 0;
- return NULL;
- }
|