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