/* * Send a command to the server using protocol v1. Returns true on success, * false on failure. */ bool internal_v1_commandv(struct remctl *r, const struct iovec *command, size_t count) { gss_buffer_desc token; size_t i; char *p; OM_uint32 data, major, minor; int status; /* Allocate room for the total message: argc, {<length><arg>}+. */ token.length = 4; for (i = 0; i < count; i++) token.length += 4 + command[i].iov_len; token.value = malloc(token.length); if (token.value == NULL) { internal_set_error(r, "cannot allocate memory: %s", strerror(errno)); return false; } /* First, the argument count. Then, each argument. */ p = token.value; data = htonl(count); memcpy(p, &data, 4); p += 4; for (i = 0; i < count; i++) { data = htonl(command[i].iov_len); memcpy(p, &data, 4); p += 4; memcpy(p, command[i].iov_base, command[i].iov_len); p += command[i].iov_len; } /* Send the result. */ status = token_send_priv(r->fd, r->context, TOKEN_DATA | TOKEN_SEND_MIC, &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; }
/* * Open a new connection to a server. Returns true on success, false on * failure. On failure, sets the error message appropriately. */ bool internal_open(struct remctl *r, const char *host, unsigned short port, const char *principal) { int status, flags; bool port_fallback = false; socket_type fd = INVALID_SOCKET; gss_buffer_desc send_tok, recv_tok, *token_ptr; gss_buffer_desc empty_token = { 0, (void *) "" }; gss_name_t name = GSS_C_NO_NAME; gss_ctx_id_t gss_context = GSS_C_NO_CONTEXT; OM_uint32 major, minor, init_minor, gss_flags; static const OM_uint32 wanted_gss_flags = (GSS_C_MUTUAL_FLAG | GSS_C_CONF_FLAG | GSS_C_INTEG_FLAG | GSS_C_REPLAY_FLAG | GSS_C_SEQUENCE_FLAG); static const OM_uint32 req_gss_flags = (GSS_C_MUTUAL_FLAG | GSS_C_CONF_FLAG | GSS_C_INTEG_FLAG); /* * If port is 0, default to trying the standard port and then falling back * on the old port. */ if (port == 0) { port = REMCTL_PORT; port_fallback = true; } /* Make the network connection. */ fd = internal_connect(r, host, port); if (fd == INVALID_SOCKET && port_fallback) fd = internal_connect(r, host, REMCTL_PORT_OLD); if (fd == INVALID_SOCKET) goto fail; r->fd = fd; /* Import the name. */ if (!internal_import_name(r, host, principal, &name)) goto fail; /* * Default to protocol version two, but if some other protocol is already * set in the remctl struct, don't override. This facility is used only * for testing currently. */ if (r->protocol == 0) r->protocol = 2; /* Send the initial negotiation token. */ status = token_send(fd, TOKEN_NOOP | TOKEN_CONTEXT_NEXT | TOKEN_PROTOCOL, &empty_token, r->timeout); if (status != TOKEN_OK) { internal_token_error(r, "sending initial token", status, 0, 0); goto fail; } /* Perform the context-establishment loop. * * On each pass through the loop, token_ptr points to the token to send to * the server (or GSS_C_NO_BUFFER on the first pass). Every generated * token is stored in send_tok which is then transmitted to the server; * every received token is stored in recv_tok, which token_ptr is then set * to, to be processed by the next call to gss_init_sec_context. * * GSS-API guarantees that send_tok's length will be non-zero if and only * if the server is expecting another token from us, and that * gss_init_sec_context returns GSS_S_CONTINUE_NEEDED if and only if the * server has another token to send us. * * We start with the assumption that we're going to do protocol v2, but if * the server ever drops TOKEN_PROTOCOL from the response, we fall back to * v1. */ token_ptr = GSS_C_NO_BUFFER; do { major = gss_init_sec_context(&init_minor, GSS_C_NO_CREDENTIAL, &gss_context, name, (const gss_OID) GSS_KRB5_MECHANISM, wanted_gss_flags, 0, NULL, token_ptr, NULL, &send_tok, &gss_flags, NULL); if (token_ptr != GSS_C_NO_BUFFER) free(recv_tok.value); /* If we have anything more to say, send it. */ if (send_tok.length != 0) { flags = TOKEN_CONTEXT; if (r->protocol > 1) flags |= TOKEN_PROTOCOL; status = token_send(fd, flags, &send_tok, r->timeout); if (status != TOKEN_OK) { internal_token_error(r, "sending token", status, major, minor); gss_release_buffer(&minor, &send_tok); goto fail; } } gss_release_buffer(&minor, &send_tok); /* On error, report the error and abort. */ if (major != GSS_S_COMPLETE && major != GSS_S_CONTINUE_NEEDED) { internal_gssapi_error(r, "initializing context", major, init_minor); goto fail; } /* If we're still expecting more, retrieve it. */ if (major == GSS_S_CONTINUE_NEEDED) { status = token_recv(fd, &flags, &recv_tok, TOKEN_MAX_LENGTH, r->timeout); if (status != TOKEN_OK) { internal_token_error(r, "receiving token", status, major, minor); goto fail; } if (r->protocol > 1 && (flags & TOKEN_PROTOCOL) != TOKEN_PROTOCOL) r->protocol = 1; token_ptr = &recv_tok; } } while (major == GSS_S_CONTINUE_NEEDED); /* * If the flags we get back from the server are bad and we're doing * protocol v2, report an error and abort. This must be done after * establishing the context, since Heimdal doesn't report all flags until * context negotiation is complete. */ if (r->protocol > 1 && (gss_flags & req_gss_flags) != req_gss_flags) { internal_set_error(r, "server did not negotiate acceptable GSS-API" " flags"); goto fail; } /* Success. Set the context in the struct remctl object. */ r->context = gss_context; r->ready = 0; gss_release_name(&minor, &name); return true; fail: if (fd != INVALID_SOCKET) socket_close(fd); r->fd = INVALID_SOCKET; if (name != GSS_C_NO_NAME) gss_release_name(&minor, &name); if (gss_context != GSS_C_NO_CONTEXT) gss_delete_sec_context(&minor, &gss_context, GSS_C_NO_BUFFER); return false; }
/* * Retrieve the output from the server using protocol version one and return * it. This function will actually be called twice, once to retrieve the * output data and once to retrieve the exit status. The old protocol * returned those together in one message, so we have to buffer the exit * status and return it on the second call. Returns a remctl output struct on * success and NULL on failure. */ struct remctl_output * internal_v1_output(struct remctl *r) { int status, flags; gss_buffer_desc token; OM_uint32 data, major, minor, length; char *p; /* * First, see if we already had an output struct. If so, this is the * second call and we should just return the exit status. */ if (r->output != NULL && !r->ready) { if (r->output->type == REMCTL_OUT_STATUS) r->output->type = REMCTL_OUT_DONE; else { internal_output_wipe(r->output); r->output->type = REMCTL_OUT_STATUS; } r->output->status = r->status; return r->output; } /* Otherwise, we have to read the token from the server. */ 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); r->context = GSS_C_NO_CONTEXT; socket_close(r->fd); r->fd = INVALID_SOCKET; r->ready = false; } return NULL; } if (flags != TOKEN_DATA) { internal_set_error(r, "unexpected token from server"); gss_release_buffer(&minor, &token); return NULL; } /* Extract the return code, message length, and data. */ if (token.length < 8) { internal_set_error(r, "malformed result token from server"); gss_release_buffer(&minor, &token); return NULL; } p = token.value; memcpy(&data, p, 4); r->status = ntohl(data); p += 4; memcpy(&data, p, 4); length = ntohl(data); p += 4; if (length != token.length - 8) { internal_set_error(r, "malformed result token from server"); gss_release_buffer(&minor, &token); return NULL; } /* * Allocate the new output token. We make another copy of the data, * unfortunately, so that we don't have to keep the token around to free * later. */ r->output = malloc(sizeof(struct remctl_output)); if (r->output == NULL) { internal_set_error(r, "cannot allocate memory: %s", strerror(errno)); gss_release_buffer(&minor, &token); return NULL; } r->output->type = REMCTL_OUT_OUTPUT; r->output->data = malloc(length); if (r->output->data == NULL) { internal_set_error(r, "cannot allocate memory: %s", strerror(errno)); gss_release_buffer(&minor, &token); return NULL; } memcpy(r->output->data, p, length); r->output->length = length; gss_release_buffer(&minor, &token); /* * We always claim everything was stdout since we have no way of knowing * better with protocol version one. */ r->output->stream = 1; /* * We can only do one round with protocol version one, so close the * connection now. */ gss_delete_sec_context(&minor, &r->context, GSS_C_NO_BUFFER); r->context = GSS_C_NO_CONTEXT; socket_close(r->fd); r->fd = INVALID_SOCKET; r->ready = false; return r->output; }