| 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 | } |
|---|