1 /*
2 * SSH agent forwarding.
3 */
4
5 #include <assert.h>
6 #include <stdio.h>
7 #include <stdlib.h>
8
9 #include "putty.h"
10 #include "ssh.h"
11 #include "pageant.h"
12 #include "sshchan.h"
13
14 typedef struct agentf {
15 SshChannel *c;
16 bufchain inbuffer;
17 agent_pending_query *pending;
18 bool input_wanted;
19 bool rcvd_eof;
20
21 Channel chan;
22 } agentf;
23
agentf_got_response(agentf * af,void * reply,int replylen)24 static void agentf_got_response(agentf *af, void *reply, int replylen)
25 {
26 af->pending = NULL;
27
28 if (!reply) {
29 /* The real agent didn't send any kind of reply at all for
30 * some reason, so fake an SSH_AGENT_FAILURE. */
31 reply = "\0\0\0\1\5";
32 replylen = 5;
33 }
34
35 sshfwd_write(af->c, reply, replylen);
36 }
37
38 static void agentf_callback(void *vctx, void *reply, int replylen);
39
agentf_try_forward(agentf * af)40 static void agentf_try_forward(agentf *af)
41 {
42 size_t datalen, length;
43 strbuf *message;
44 unsigned char msglen[4];
45 void *reply;
46 int replylen;
47
48 /*
49 * Don't try to parallelise agent requests. Wait for each one to
50 * return before attempting the next.
51 */
52 if (af->pending)
53 return;
54
55 /*
56 * If the outgoing side of the channel connection is currently
57 * throttled, don't submit any new forwarded requests to the real
58 * agent. This causes the input side of the agent forwarding not
59 * to be emptied, exerting the required back-pressure on the
60 * remote client, and encouraging it to read our responses before
61 * sending too many more requests.
62 */
63 if (!af->input_wanted)
64 return;
65
66 while (1) {
67 /*
68 * Try to extract a complete message from the input buffer.
69 */
70 datalen = bufchain_size(&af->inbuffer);
71 if (datalen < 4)
72 break; /* not even a length field available yet */
73
74 bufchain_fetch(&af->inbuffer, msglen, 4);
75 length = GET_32BIT_MSB_FIRST(msglen);
76
77 if (length > AGENT_MAX_MSGLEN-4) {
78 /*
79 * If the remote has sent a message that's just _too_
80 * long, we should reject it in advance of seeing the rest
81 * of the incoming message, and also close the connection
82 * for good measure (which avoids us having to faff about
83 * with carefully ignoring just the right number of bytes
84 * from the overlong message).
85 */
86 agentf_got_response(af, NULL, 0);
87 sshfwd_write_eof(af->c);
88 return;
89 }
90
91 if (length > datalen - 4)
92 break; /* a whole message is not yet available */
93
94 bufchain_consume(&af->inbuffer, 4);
95
96 message = strbuf_new_for_agent_query();
97 bufchain_fetch_consume(
98 &af->inbuffer, strbuf_append(message, length), length);
99 af->pending = agent_query(
100 message, &reply, &replylen, agentf_callback, af);
101 strbuf_free(message);
102
103 if (af->pending)
104 return; /* agent_query promised to reply in due course */
105
106 /*
107 * If the agent gave us an answer immediately, pass it
108 * straight on and go round this loop again.
109 */
110 agentf_got_response(af, reply, replylen);
111 sfree(reply);
112 }
113
114 /*
115 * If we get here (i.e. we left the above while loop via 'break'
116 * rather than 'return'), that means we've determined that the
117 * input buffer for the agent forwarding connection doesn't
118 * contain a complete request.
119 *
120 * So if there's potentially more data to come, we can return now,
121 * and wait for the remote client to send it. But if the remote
122 * has sent EOF, it would be a mistake to do that, because we'd be
123 * waiting a long time. So this is the moment to check for EOF,
124 * and respond appropriately.
125 */
126 if (af->rcvd_eof)
127 sshfwd_write_eof(af->c);
128 }
129
agentf_callback(void * vctx,void * reply,int replylen)130 static void agentf_callback(void *vctx, void *reply, int replylen)
131 {
132 agentf *af = (agentf *)vctx;
133
134 agentf_got_response(af, reply, replylen);
135 sfree(reply);
136
137 /*
138 * Now try to extract and send further messages from the channel's
139 * input-side buffer.
140 */
141 agentf_try_forward(af);
142 }
143
144 static void agentf_free(Channel *chan);
145 static size_t agentf_send(Channel *chan, bool is_stderr, const void *, size_t);
146 static void agentf_send_eof(Channel *chan);
147 static char *agentf_log_close_msg(Channel *chan);
148 static void agentf_set_input_wanted(Channel *chan, bool wanted);
149
150 static const ChannelVtable agentf_channelvt = {
151 .free = agentf_free,
152 .open_confirmation = chan_remotely_opened_confirmation,
153 .open_failed = chan_remotely_opened_failure,
154 .send = agentf_send,
155 .send_eof = agentf_send_eof,
156 .set_input_wanted = agentf_set_input_wanted,
157 .log_close_msg = agentf_log_close_msg,
158 .want_close = chan_default_want_close,
159 .rcvd_exit_status = chan_no_exit_status,
160 .rcvd_exit_signal = chan_no_exit_signal,
161 .rcvd_exit_signal_numeric = chan_no_exit_signal_numeric,
162 .run_shell = chan_no_run_shell,
163 .run_command = chan_no_run_command,
164 .run_subsystem = chan_no_run_subsystem,
165 .enable_x11_forwarding = chan_no_enable_x11_forwarding,
166 .enable_agent_forwarding = chan_no_enable_agent_forwarding,
167 .allocate_pty = chan_no_allocate_pty,
168 .set_env = chan_no_set_env,
169 .send_break = chan_no_send_break,
170 .send_signal = chan_no_send_signal,
171 .change_window_size = chan_no_change_window_size,
172 .request_response = chan_no_request_response,
173 };
174
agentf_new(SshChannel * c)175 Channel *agentf_new(SshChannel *c)
176 {
177 agentf *af = snew(agentf);
178 af->c = c;
179 af->chan.vt = &agentf_channelvt;
180 af->chan.initial_fixed_window_size = 0;
181 af->rcvd_eof = false;
182 bufchain_init(&af->inbuffer);
183 af->pending = NULL;
184 af->input_wanted = true;
185 return &af->chan;
186 }
187
agentf_free(Channel * chan)188 static void agentf_free(Channel *chan)
189 {
190 assert(chan->vt == &agentf_channelvt);
191 agentf *af = container_of(chan, agentf, chan);
192
193 if (af->pending)
194 agent_cancel_query(af->pending);
195 bufchain_clear(&af->inbuffer);
196 sfree(af);
197 }
198
agentf_send(Channel * chan,bool is_stderr,const void * data,size_t length)199 static size_t agentf_send(Channel *chan, bool is_stderr,
200 const void *data, size_t length)
201 {
202 assert(chan->vt == &agentf_channelvt);
203 agentf *af = container_of(chan, agentf, chan);
204 bufchain_add(&af->inbuffer, data, length);
205 agentf_try_forward(af);
206
207 /*
208 * We exert back-pressure on an agent forwarding client if and
209 * only if we're waiting for the response to an asynchronous agent
210 * request. This prevents the client running out of window while
211 * receiving the _first_ message, but means that if any message
212 * takes time to process, the client will be discouraged from
213 * sending an endless stream of further ones after it.
214 */
215 return (af->pending ? bufchain_size(&af->inbuffer) : 0);
216 }
217
agentf_send_eof(Channel * chan)218 static void agentf_send_eof(Channel *chan)
219 {
220 assert(chan->vt == &agentf_channelvt);
221 agentf *af = container_of(chan, agentf, chan);
222
223 af->rcvd_eof = true;
224
225 /* Call try_forward, which will respond to the EOF now if
226 * appropriate, or wait until the queue of outstanding requests is
227 * dealt with if not. */
228 agentf_try_forward(af);
229 }
230
agentf_log_close_msg(Channel * chan)231 static char *agentf_log_close_msg(Channel *chan)
232 {
233 return dupstr("Agent-forwarding connection closed");
234 }
235
agentf_set_input_wanted(Channel * chan,bool wanted)236 static void agentf_set_input_wanted(Channel *chan, bool wanted)
237 {
238 assert(chan->vt == &agentf_channelvt);
239 agentf *af = container_of(chan, agentf, chan);
240
241 af->input_wanted = wanted;
242
243 /* Agent forwarding channels are buffer-managed by not asking the
244 * agent questions if the SSH channel isn't accepting input. So if
245 * it's started again, we should ask a question if we have one
246 * pending.. */
247 if (wanted)
248 agentf_try_forward(af);
249 }
250