]> asedeno.scripts.mit.edu Git - PuTTY.git/blob - unix/uxagentc.c
326ee66b832439e35cb40a471339bc989074669c
[PuTTY.git] / unix / uxagentc.c
1 /*
2  * SSH agent client code.
3  */
4
5 #include <stdio.h>
6 #include <stdlib.h>
7 #include <assert.h>
8 #include <unistd.h>
9 #include <sys/socket.h>
10 #include <sys/un.h>
11 #include <fcntl.h>
12
13 #include "putty.h"
14 #include "misc.h"
15 #include "tree234.h"
16 #include "puttymem.h"
17
18 int agent_exists(void)
19 {
20     const char *p = getenv("SSH_AUTH_SOCK");
21     if (p && *p)
22         return TRUE;
23     return FALSE;
24 }
25
26 static tree234 *agent_connections;
27 struct agent_connection {
28     int fd;
29     char *retbuf;
30     char sizebuf[4];
31     int retsize, retlen;
32     void (*callback)(void *, void *, int);
33     void *callback_ctx;
34 };
35 static int agent_conncmp(void *av, void *bv)
36 {
37     struct agent_connection *a = (struct agent_connection *) av;
38     struct agent_connection *b = (struct agent_connection *) bv;
39     if (a->fd < b->fd)
40         return -1;
41     if (a->fd > b->fd)
42         return +1;
43     return 0;
44 }
45 static int agent_connfind(void *av, void *bv)
46 {
47     int afd = *(int *) av;
48     struct agent_connection *b = (struct agent_connection *) bv;
49     if (afd < b->fd)
50         return -1;
51     if (afd > b->fd)
52         return +1;
53     return 0;
54 }
55
56 /*
57  * Attempt to read from an agent socket fd. Returns 0 if the expected
58  * response is as yet incomplete; returns 1 if it's either complete
59  * (conn->retbuf non-NULL and filled with something useful) or has
60  * failed totally (conn->retbuf is NULL).
61  */
62 static int agent_try_read(struct agent_connection *conn)
63 {
64     int ret;
65
66     ret = read(conn->fd, conn->retbuf+conn->retlen,
67                conn->retsize-conn->retlen);
68     if (ret <= 0) {
69         if (conn->retbuf != conn->sizebuf) sfree(conn->retbuf);
70         conn->retbuf = NULL;
71         conn->retlen = 0;
72         return 1;
73     }
74     conn->retlen += ret;
75     if (conn->retsize == 4 && conn->retlen == 4) {
76         conn->retsize = toint(GET_32BIT(conn->retbuf) + 4);
77         if (conn->retsize <= 0) {
78             conn->retbuf = NULL;
79             conn->retlen = 0;
80             return -1;                 /* way too large */
81         }
82         assert(conn->retbuf == conn->sizebuf);
83         conn->retbuf = snewn(conn->retsize, char);
84         memcpy(conn->retbuf, conn->sizebuf, 4);
85     }
86
87     if (conn->retlen < conn->retsize)
88         return 0;                      /* more data to come */
89
90     return 1;
91 }
92
93 static int agent_select_result(int fd, int event)
94 {
95     struct agent_connection *conn;
96
97     assert(event == 1);                /* not selecting for anything but R */
98
99     conn = find234(agent_connections, &fd, agent_connfind);
100     if (!conn) {
101         uxsel_del(fd);
102         return 1;
103     }
104
105     if (!agent_try_read(conn))
106         return 0;                      /* more data to come */
107
108     /*
109      * We have now completed the agent query. Do the callback, and
110      * clean up. (Of course we don't free retbuf, since ownership
111      * of that passes to the callback.)
112      */
113     conn->callback(conn->callback_ctx, conn->retbuf, conn->retlen);
114     uxsel_del(fd);
115     close(fd);
116     del234(agent_connections, conn);
117     sfree(conn);
118     return 0;
119 }
120
121 int agent_query(void *in, int inlen, void **out, int *outlen,
122                 void (*callback)(void *, void *, int), void *callback_ctx)
123 {
124     char *name;
125     int sock;
126     struct sockaddr_un addr;
127     int done;
128     struct agent_connection *conn;
129
130     name = getenv("SSH_AUTH_SOCK");
131     if (!name)
132         goto failure;
133
134     sock = socket(PF_UNIX, SOCK_STREAM, 0);
135     if (sock < 0) {
136         perror("socket(PF_UNIX)");
137         exit(1);
138     }
139
140     cloexec(sock);
141
142     addr.sun_family = AF_UNIX;
143     strncpy(addr.sun_path, name, sizeof(addr.sun_path));
144     if (connect(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
145         close(sock);
146         goto failure;
147     }
148
149     for (done = 0; done < inlen ;) {
150         int ret = write(sock, (char *)in + done, inlen - done);
151         if (ret <= 0) {
152             close(sock);
153             goto failure;
154         }
155         done += ret;
156     }
157
158     conn = snew(struct agent_connection);
159     conn->fd = sock;
160     conn->retbuf = conn->sizebuf;
161     conn->retsize = 4;
162     conn->retlen = 0;
163     conn->callback = callback;
164     conn->callback_ctx = callback_ctx;
165
166     if (!callback) {
167         /*
168          * Bodge to permit making deliberately synchronous agent
169          * requests. Used by Unix Pageant in command-line client mode,
170          * which is legit because it really is true that no other part
171          * of the program is trying to get anything useful done
172          * simultaneously. But this special case shouldn't be used in
173          * any more general program.
174          */
175         no_nonblock(conn->fd);
176         while (!agent_try_read(conn))
177             /* empty loop body */;
178
179         *out = conn->retbuf;
180         *outlen = conn->retlen;
181         sfree(conn);
182         return 1;
183     }
184
185     /*
186      * Otherwise do it properly: add conn to the tree of agent
187      * connections currently in flight, return 0 to indicate that the
188      * response hasn't been received yet, and call the callback when
189      * select_result comes back to us.
190      */
191     if (!agent_connections)
192         agent_connections = newtree234(agent_conncmp);
193     add234(agent_connections, conn);
194
195     uxsel_set(sock, 1, agent_select_result);
196     return 0;
197
198     failure:
199     *out = NULL;
200     *outlen = 0;
201     return 1;
202 }