/* * Takes the client struct and the server configuration and handles a client * request. Reads a command from the client, checks the ACL, runs the command * if appropriate, and sends any output back to the client. */ void server_v1_handle_messages(struct client *client, struct config *config) { gss_buffer_desc token; OM_uint32 major, minor; struct iovec **argv = NULL; int status, flags; /* Receive the message. */ status = token_recv_priv(client->fd, client->context, &flags, &token, TOKEN_MAX_LENGTH, TIMEOUT, &major, &minor); if (status != TOKEN_OK) { warn_token("receiving command token", status, major, minor); if (status == TOKEN_FAIL_LARGE) server_send_error(client, ERROR_TOOMUCH_DATA, "Too much data"); else if (status != TOKEN_FAIL_EOF) server_send_error(client, ERROR_BAD_TOKEN, "Invalid token"); return; } /* Check the data size. */ if (token.length > TOKEN_MAX_DATA) { warn("command data length %lu exceeds 64KB", (unsigned long) token.length); server_send_error(client, ERROR_TOOMUCH_DATA, "Too much data"); gss_release_buffer(&minor, &token); return; } /* * Do the shared parsing of the message. This code is identical to the * code for v2 (v2 just pulls more data off the front of the token first). */ argv = server_parse_command(client, token.value, token.length); gss_release_buffer(&minor, &token); if (argv == NULL) return; /* * Check the ACL and existence of the command, run the command if * possible, and accumulate the output in the client struct. */ server_run_command(client, config, argv); server_free_command(argv); }
/* * Given the client struct, a buffer of data to send, and the exit status of a * command, send a protocol v1 output token back to the client. Returns true * on success and false on failure (and logs a message on failure). */ bool server_v1_send_output(struct client *client, struct evbuffer *output, int exit_status) { gss_buffer_desc token; size_t outlen; char *p; OM_uint32 tmp, major, minor; int status; /* Allocate room for the total message. */ outlen = evbuffer_get_length(output); token.length = 4 + 4 + outlen; token.value = xmalloc(token.length); /* Fill in the token. */ p = token.value; tmp = htonl(exit_status); memcpy(p, &tmp, 4); p += 4; tmp = htonl(outlen); memcpy(p, &tmp, 4); p += 4; if (evbuffer_remove(output, p, outlen) < 0) die("internal error: cannot move data from output buffer"); /* Send the token. */ status = token_send_priv(client->fd, client->context, TOKEN_DATA, &token, TIMEOUT, &major, &minor); if (status != TOKEN_OK) { warn_token("sending output token", status, major, minor); free(token.value); return false; } free(token.value); return true; }
/* * Create a new client struct from a file descriptor and establish a GSS-API * context as a specified service with an incoming client and fills out the * client struct. Returns a new client struct on success and NULL on failure, * logging an appropriate error message. */ struct client * server_new_client(int fd, gss_cred_id_t creds) { struct client *client; struct sockaddr_storage ss; socklen_t socklen; size_t length; char *buffer; gss_buffer_desc send_tok, recv_tok, name_buf; gss_name_t name = GSS_C_NO_NAME; gss_OID doid; OM_uint32 major = 0; OM_uint32 minor = 0; OM_uint32 acc_minor; int flags, status; static const OM_uint32 req_gss_flags = (GSS_C_MUTUAL_FLAG | GSS_C_CONF_FLAG | GSS_C_INTEG_FLAG); /* Create and initialize a new client struct. */ client = xcalloc(1, sizeof(struct client)); client->fd = fd; client->context = GSS_C_NO_CONTEXT; client->user = NULL; client->output = NULL; client->hostname = NULL; client->ipaddress = NULL; /* Fill in hostname and IP address. */ socklen = sizeof(ss); if (getpeername(fd, (struct sockaddr *) &ss, &socklen) != 0) { syswarn("cannot get peer address"); goto fail; } length = INET6_ADDRSTRLEN; buffer = xmalloc(length); client->ipaddress = buffer; status = getnameinfo((struct sockaddr *) &ss, socklen, buffer, length, NULL, 0, NI_NUMERICHOST); if (status != 0) { syswarn("cannot translate IP address of client: %s", gai_strerror(status)); goto fail; } length = NI_MAXHOST; buffer = xmalloc(length); status = getnameinfo((struct sockaddr *) &ss, socklen, buffer, length, NULL, 0, NI_NAMEREQD); if (status == 0) client->hostname = buffer; else free(buffer); /* Accept the initial (worthless) token. */ status = token_recv(client->fd, &flags, &recv_tok, TOKEN_MAX_LENGTH, TIMEOUT); if (status != TOKEN_OK) { warn_token("receiving initial token", status, major, minor); goto fail; } free(recv_tok.value); if (flags == (TOKEN_NOOP | TOKEN_CONTEXT_NEXT | TOKEN_PROTOCOL)) client->protocol = 2; else if (flags == (TOKEN_NOOP | TOKEN_CONTEXT_NEXT)) client->protocol = 1; else { warn("bad token flags %d in initial token", flags); goto fail; } /* Now, do the real work of negotiating the context. */ do { status = token_recv(client->fd, &flags, &recv_tok, TOKEN_MAX_LENGTH, TIMEOUT); if (status != TOKEN_OK) { warn_token("receiving context token", status, major, minor); goto fail; } if (flags == TOKEN_CONTEXT) client->protocol = 1; else if (flags != (TOKEN_CONTEXT | TOKEN_PROTOCOL)) { warn("bad token flags %d in context token", flags); free(recv_tok.value); goto fail; } debug("received context token (size=%lu)", (unsigned long) recv_tok.length); major = gss_accept_sec_context(&acc_minor, &client->context, creds, &recv_tok, GSS_C_NO_CHANNEL_BINDINGS, &name, &doid, &send_tok, &client->flags, NULL, NULL); free(recv_tok.value); /* Send back a token if we need to. */ if (send_tok.length != 0) { debug("sending context token (size=%lu)", (unsigned long) send_tok.length); flags = TOKEN_CONTEXT; if (client->protocol > 1) flags |= TOKEN_PROTOCOL; status = token_send(client->fd, flags, &send_tok, TIMEOUT); if (status != TOKEN_OK) { warn_token("sending context token", status, major, minor); gss_release_buffer(&minor, &send_tok); goto fail; } gss_release_buffer(&minor, &send_tok); } /* Bail out if we lose. */ if (major != GSS_S_COMPLETE && major != GSS_S_CONTINUE_NEEDED) { warn_gssapi("while accepting context", major, acc_minor); goto fail; } if (major == GSS_S_CONTINUE_NEEDED) debug("continue needed while accepting context"); } while (major == GSS_S_CONTINUE_NEEDED); /* Make sure that the appropriate context flags are set. */ if (client->protocol > 1) { if ((client->flags & req_gss_flags) != req_gss_flags) { warn("client did not negotiate appropriate GSS-API flags"); goto fail; } } /* Get the display version of the client name and store it. */ major = gss_display_name(&minor, name, &name_buf, &doid); if (major != GSS_S_COMPLETE) { warn_gssapi("while displaying client name", major, minor); goto fail; } major = gss_release_name(&minor, &name); client->user = xstrndup(name_buf.value, name_buf.length); gss_release_buffer(&minor, &name_buf); return client; fail: if (client->context != GSS_C_NO_CONTEXT) gss_delete_sec_context(&minor, &client->context, GSS_C_NO_BUFFER); if (name != GSS_C_NO_NAME) gss_release_name(&minor, &name); if (client->ipaddress != NULL) free(client->ipaddress); if (client->hostname != NULL) free(client->hostname); free(client); return NULL; }