1 | /* |
---|
2 | * Protocol v2, client implementation. |
---|
3 | * |
---|
4 | * This is the client implementation of the new v2 protocol. It's fairly |
---|
5 | * close to the regular remctl API. |
---|
6 | * |
---|
7 | * Written by Russ Allbery <rra@stanford.edu> |
---|
8 | * Based on work by Anton Ushakov |
---|
9 | * Copyright 2002, 2003, 2004, 2005, 2006, 2007, 2008 |
---|
10 | * Board of Trustees, Leland Stanford Jr. University |
---|
11 | * |
---|
12 | * See LICENSE for licensing terms. |
---|
13 | */ |
---|
14 | |
---|
15 | #include <config.h> |
---|
16 | #include <portable/system.h> |
---|
17 | #include <portable/gssapi.h> |
---|
18 | #include <portable/socket.h> |
---|
19 | #include <portable/uio.h> |
---|
20 | |
---|
21 | #include <errno.h> |
---|
22 | |
---|
23 | #include <client/internal.h> |
---|
24 | #include <client/remctl.h> |
---|
25 | #include <util/util.h> |
---|
26 | |
---|
27 | |
---|
28 | /* |
---|
29 | * Send a command to the server using protocol v2. Returns true on success, |
---|
30 | * false on failure. |
---|
31 | * |
---|
32 | * All of the complexity in this function comes from implementing command |
---|
33 | * continuation. The protocol specifies that commands can be continued by |
---|
34 | * tresting the command as one huge token, chopping it into as many pieces as |
---|
35 | * desired, and putting the MESSAGE_COMMAND header on each piece with the |
---|
36 | * appropriate continue status. We don't take full advantage of that (we |
---|
37 | * don't, for instance, ever split numbers across token boundaries), but we do |
---|
38 | * use this to handle commands where all the data is longer than |
---|
39 | * TOKEN_MAX_DATA. |
---|
40 | */ |
---|
41 | bool |
---|
42 | internal_v2_commandv(struct remctl *r, const struct iovec *command, |
---|
43 | size_t count) |
---|
44 | { |
---|
45 | size_t length, iov, offset, sent, left, delta; |
---|
46 | gss_buffer_desc token; |
---|
47 | char *p; |
---|
48 | OM_uint32 data, major, minor; |
---|
49 | int status; |
---|
50 | |
---|
51 | /* Determine the total length of the message. */ |
---|
52 | length = 4; |
---|
53 | for (iov = 0; iov < count; iov++) |
---|
54 | length += 4 + command[iov].iov_len; |
---|
55 | |
---|
56 | /* |
---|
57 | * Now, loop until we've conveyed the entire message. Each token we send |
---|
58 | * to the server must include the standard header and the continue status. |
---|
59 | * The first token then has the argument count, and the remainder of the |
---|
60 | * command consists of pairs of argument length and argument data. |
---|
61 | * |
---|
62 | * If the entire message length plus the overhead for the header is less |
---|
63 | * than TOKEN_MAX_DATA, we send it in one go. Otherwise, each time |
---|
64 | * through this loop, we pull off as much data as we can. We break the |
---|
65 | * tokens either in the middle of an argument or just before an argument |
---|
66 | * length; we never send part of the argument length number and we always |
---|
67 | * include at least one byte of the argument after the argument length. |
---|
68 | * The protocol is more lenient, but those constraints make bookkeeping |
---|
69 | * easier. |
---|
70 | * |
---|
71 | * iov is the index of the argument we're currently sending. offset is |
---|
72 | * the amount of that argument data we've already sent. sent holds the |
---|
73 | * total length sent so far so that we can tell when we're done. |
---|
74 | */ |
---|
75 | iov = 0; |
---|
76 | offset = 0; |
---|
77 | sent = 0; |
---|
78 | while (sent < length) { |
---|
79 | if (length - sent > TOKEN_MAX_DATA - 4) |
---|
80 | token.length = TOKEN_MAX_DATA; |
---|
81 | else |
---|
82 | token.length = length - sent + 4; |
---|
83 | token.value = malloc(token.length); |
---|
84 | if (token.value == NULL) { |
---|
85 | internal_set_error(r, "cannot allocate memory: %s", |
---|
86 | strerror(errno)); |
---|
87 | return false; |
---|
88 | } |
---|
89 | left = token.length - 4; |
---|
90 | |
---|
91 | /* Each token begins with the protocol version and message type. */ |
---|
92 | p = token.value; |
---|
93 | p[0] = 2; |
---|
94 | p[1] = MESSAGE_COMMAND; |
---|
95 | p += 2; |
---|
96 | |
---|
97 | /* Keep-alive flag. Always set to true for now. */ |
---|
98 | *p = 1; |
---|
99 | p++; |
---|
100 | |
---|
101 | /* Continue status. */ |
---|
102 | if (token.length == length - sent + 4) |
---|
103 | *p = (sent == 0) ? 0 : 3; |
---|
104 | else |
---|
105 | *p = (sent == 0) ? 1 : 2; |
---|
106 | p++; |
---|
107 | |
---|
108 | /* Argument count if we haven't sent anything yet. */ |
---|
109 | if (sent == 0) { |
---|
110 | data = htonl(count); |
---|
111 | memcpy(p, &data, 4); |
---|
112 | p += 4; |
---|
113 | sent += 4; |
---|
114 | left -= 4; |
---|
115 | } |
---|
116 | |
---|
117 | /* |
---|
118 | * Now, as many arguments as will fit. If offset is 0, we're at the |
---|
119 | * beginning of an argument and need to send the length. Make sure, |
---|
120 | * if we're at the beginning of an argument, that we can add at least |
---|
121 | * five octets to this token. The length plus at least one octet must |
---|
122 | * fit (or just the length if that argument is zero-length). |
---|
123 | */ |
---|
124 | for (; iov < count; iov++) { |
---|
125 | if (offset == 0) { |
---|
126 | if (left < 4 || (left < 5 && command[iov].iov_len > 0)) |
---|
127 | break; |
---|
128 | data = htonl(command[iov].iov_len); |
---|
129 | memcpy(p, &data, 4); |
---|
130 | p += 4; |
---|
131 | sent += 4; |
---|
132 | left -= 4; |
---|
133 | } |
---|
134 | if (left >= command[iov].iov_len - offset) |
---|
135 | delta = command[iov].iov_len - offset; |
---|
136 | else |
---|
137 | delta = left; |
---|
138 | memcpy(p, (char *) command[iov].iov_base + offset, delta); |
---|
139 | p += delta; |
---|
140 | sent += delta; |
---|
141 | offset += delta; |
---|
142 | left -= delta; |
---|
143 | if (offset < (size_t) command[iov].iov_len) |
---|
144 | break; |
---|
145 | offset = 0; |
---|
146 | } |
---|
147 | |
---|
148 | /* Send the result. */ |
---|
149 | token.length -= left; |
---|
150 | status = token_send_priv(r->fd, r->context, |
---|
151 | TOKEN_DATA | TOKEN_PROTOCOL, &token, |
---|
152 | &major, &minor); |
---|
153 | if (status != TOKEN_OK) { |
---|
154 | internal_token_error(r, "sending token", status, major, minor); |
---|
155 | free(token.value); |
---|
156 | return false; |
---|
157 | } |
---|
158 | free(token.value); |
---|
159 | } |
---|
160 | r->ready = true; |
---|
161 | return true; |
---|
162 | } |
---|
163 | |
---|
164 | |
---|
165 | /* |
---|
166 | * Send a quit command to the server using protocol v2. Returns true on |
---|
167 | * success, false on failure. |
---|
168 | */ |
---|
169 | bool |
---|
170 | internal_v2_quit(struct remctl *r) |
---|
171 | { |
---|
172 | gss_buffer_desc token; |
---|
173 | char buffer[2] = { 2, MESSAGE_QUIT }; |
---|
174 | OM_uint32 major, minor; |
---|
175 | int status; |
---|
176 | |
---|
177 | token.length = 1 + 1; |
---|
178 | token.value = buffer; |
---|
179 | status = token_send_priv(r->fd, r->context, TOKEN_DATA | TOKEN_PROTOCOL, |
---|
180 | &token, &major, &minor); |
---|
181 | if (status != TOKEN_OK) { |
---|
182 | internal_token_error(r, "sending token", status, major, minor); |
---|
183 | return false; |
---|
184 | } |
---|
185 | return true; |
---|
186 | } |
---|
187 | |
---|
188 | |
---|
189 | /* |
---|
190 | * Read a string from a server token, with its length starting at the given |
---|
191 | * offset, and store it in newly allocated memory in the remctl struct. |
---|
192 | * Returns true on success and false on any failure (also setting the error). |
---|
193 | */ |
---|
194 | static bool |
---|
195 | internal_v2_read_string(struct remctl *r, gss_buffer_t token, size_t offset) |
---|
196 | { |
---|
197 | size_t size; |
---|
198 | OM_uint32 data; |
---|
199 | const char *p; |
---|
200 | |
---|
201 | p = (const char *) token->value + offset; |
---|
202 | memcpy(&data, p, 4); |
---|
203 | p += 4; |
---|
204 | size = ntohl(data); |
---|
205 | if (size != token->length - (p - (char *) token->value)) { |
---|
206 | internal_set_error(r, "malformed result token from server"); |
---|
207 | return false; |
---|
208 | } |
---|
209 | r->output->data = malloc(size); |
---|
210 | if (r->output->data == NULL) { |
---|
211 | internal_set_error(r, "cannot allocate memory: %s", strerror(errno)); |
---|
212 | return false; |
---|
213 | } |
---|
214 | memcpy(r->output->data, p, size); |
---|
215 | r->output->length = size; |
---|
216 | return true; |
---|
217 | } |
---|
218 | |
---|
219 | |
---|
220 | /* |
---|
221 | * Retrieve the output from the server using protocol v2 and return it. This |
---|
222 | * function may be called any number of times; if the last packet we got from |
---|
223 | * the server was a REMCTL_OUT_STATUS or REMCTL_OUT_ERROR, we'll return |
---|
224 | * REMCTL_OUT_DONE from that point forward. Returns a remctl output struct on |
---|
225 | * success and NULL on failure. |
---|
226 | */ |
---|
227 | struct remctl_output * |
---|
228 | internal_v2_output(struct remctl *r) |
---|
229 | { |
---|
230 | int status, flags; |
---|
231 | gss_buffer_desc token; |
---|
232 | OM_uint32 data, major, minor; |
---|
233 | char *p; |
---|
234 | int type; |
---|
235 | |
---|
236 | /* |
---|
237 | * Initialize our output. If we're not ready to read more data from the |
---|
238 | * server, return REMCTL_OUT_DONE. |
---|
239 | */ |
---|
240 | if (r->output == NULL) { |
---|
241 | r->output = malloc(sizeof(struct remctl_output)); |
---|
242 | if (r->output == NULL) { |
---|
243 | internal_set_error(r, "cannot allocate memory: %s", |
---|
244 | strerror(errno)); |
---|
245 | return NULL; |
---|
246 | } |
---|
247 | r->output->data = NULL; |
---|
248 | } |
---|
249 | internal_output_wipe(r->output); |
---|
250 | if (!r->ready) |
---|
251 | return r->output; |
---|
252 | |
---|
253 | /* Otherwise, we have to read the token from the server. */ |
---|
254 | status = token_recv_priv(r->fd, r->context, &flags, &token, |
---|
255 | TOKEN_MAX_LENGTH, &major, &minor); |
---|
256 | if (status != TOKEN_OK) { |
---|
257 | internal_token_error(r, "receiving token", status, major, minor); |
---|
258 | if (status == TOKEN_FAIL_EOF) { |
---|
259 | socket_close(r->fd); |
---|
260 | r->fd = -1; |
---|
261 | } |
---|
262 | return NULL; |
---|
263 | } |
---|
264 | if (flags != (TOKEN_DATA | TOKEN_PROTOCOL)) { |
---|
265 | internal_set_error(r, "unexpected token from server"); |
---|
266 | goto fail; |
---|
267 | } |
---|
268 | if (token.length < 2) { |
---|
269 | internal_set_error(r, "malformed result token from server"); |
---|
270 | goto fail; |
---|
271 | } |
---|
272 | |
---|
273 | /* Extract the message protocol and type. */ |
---|
274 | p = token.value; |
---|
275 | if (p[0] != 2) { |
---|
276 | internal_set_error(r, "unexpected protocol %d from server", p[0]); |
---|
277 | goto fail; |
---|
278 | } |
---|
279 | type = p[1]; |
---|
280 | |
---|
281 | /* Now, what we do depends on the message type. */ |
---|
282 | switch (type) { |
---|
283 | case MESSAGE_OUTPUT: |
---|
284 | if (token.length < 2 + 5) { |
---|
285 | internal_set_error(r, "malformed result token from server"); |
---|
286 | goto fail; |
---|
287 | } |
---|
288 | r->output->type = REMCTL_OUT_OUTPUT; |
---|
289 | if (p[2] != 1 && p[2] != 2) { |
---|
290 | internal_set_error(r, "unexpected stream %d from server", p[0]); |
---|
291 | goto fail; |
---|
292 | } |
---|
293 | r->output->stream = p[2]; |
---|
294 | if (!internal_v2_read_string(r, &token, 3)) |
---|
295 | goto fail; |
---|
296 | break; |
---|
297 | |
---|
298 | case MESSAGE_STATUS: |
---|
299 | if (token.length != 2 + 1) { |
---|
300 | internal_set_error(r, "malformed result token from server"); |
---|
301 | goto fail; |
---|
302 | } |
---|
303 | r->output->type = REMCTL_OUT_STATUS; |
---|
304 | r->output->status = p[2]; |
---|
305 | r->ready = 0; |
---|
306 | break; |
---|
307 | |
---|
308 | case MESSAGE_ERROR: |
---|
309 | if (token.length < 2 + 8) { |
---|
310 | internal_set_error(r, "malformed result token from server"); |
---|
311 | goto fail; |
---|
312 | } |
---|
313 | r->output->type = REMCTL_OUT_ERROR; |
---|
314 | memcpy(&data, p + 2, 4); |
---|
315 | r->output->error = ntohl(data); |
---|
316 | if (!internal_v2_read_string(r, &token, 6)) |
---|
317 | goto fail; |
---|
318 | r->ready = 0; |
---|
319 | break; |
---|
320 | |
---|
321 | default: |
---|
322 | internal_set_error(r, "unknown message type %d from server", type); |
---|
323 | goto fail; |
---|
324 | } |
---|
325 | |
---|
326 | /* We've finished analyzing the packet. Return the results. */ |
---|
327 | gss_release_buffer(&minor, &token); |
---|
328 | return r->output; |
---|
329 | |
---|
330 | fail: |
---|
331 | gss_release_buffer(&minor, &token); |
---|
332 | return NULL; |
---|
333 | } |
---|