/* * 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; }
/* * 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; }
int main(void) { pid_t child; socket_type server, client; int status, flags; char buffer[20]; ssize_t length; gss_buffer_desc result; alarm(20); plan(12); if (chdir(getenv("C_TAP_BUILD")) < 0) sysbail("can't chdir to C_TAP_BUILD"); unlink("server-ready"); child = fork(); if (child < 0) sysbail("cannot fork"); else if (child == 0) { server = create_server(); send_regular_token(server); socket_close(server); exit(0); } else { client = create_client(); length = read(client, buffer, 12); is_int(10, length, "received token has correct length"); ok(memcmp(buffer, token, 10) == 0, "...and correct data"); waitpid(child, NULL, 0); socket_close(client); } unlink("server-ready"); child = fork(); if (child < 0) sysbail("cannot fork"); else if (child == 0) { server = create_server(); send_hand_token(server); socket_close(server); exit(0); } else { client = create_client(); status = token_recv(client, &flags, &result, 5, 0); is_int(TOKEN_OK, status, "received hand-rolled token"); is_int(3, flags, "...with right flags"); is_int(5, result.length, "...and right length"); ok(memcmp(result.value, "hello", 5) == 0, "...and right data"); free(result.value); waitpid(child, NULL, 0); socket_close(client); } /* Send a token with a length of one, but no following data. */ unlink("server-ready"); child = fork(); if (child < 0) sysbail("cannot fork"); else if (child == 0) { server = create_server(); socket_xwrite(server, "\0\0\0\0\1", 5); socket_close(server); exit(0); } else { client = create_client(); status = token_recv(client, &flags, &result, 200, 0); is_int(TOKEN_FAIL_EOF, status, "receive invalid token"); waitpid(child, NULL, 0); socket_close(client); } /* Send a token larger than our token size limit. */ unlink("server-ready"); child = fork(); if (child < 0) sysbail("cannot fork"); else if (child == 0) { server = create_server(); send_hand_token(server); socket_close(server); exit(0); } else { client = create_client(); status = token_recv(client, &flags, &result, 4, 0); is_int(TOKEN_FAIL_LARGE, status, "receive too-large token"); waitpid(child, NULL, 0); socket_close(client); } /* Send EOF when we were expecting a token. */ unlink("server-ready"); child = fork(); if (child < 0) sysbail("cannot fork"); else if (child == 0) { server = create_server(); socket_close(server); exit(0); } else { client = create_client(); status = token_recv(client, &flags, &result, 4, 0); is_int(TOKEN_FAIL_EOF, status, "receive end of file"); waitpid(child, NULL, 0); socket_close(client); } /* * Test a timeout on sending a token. We have to send a large enough * token that the network layer doesn't just buffer it. */ unlink("server-ready"); child = fork(); if (child < 0) sysbail("cannot fork"); else if (child == 0) { server = create_server(); sleep(3); socket_close(server); exit(0); } else { result.value = bmalloc(8192 * 1024); memset(result.value, 'a', 8192 * 1024); result.length = 8192 * 1024; client = create_client(); status = token_send(client, 3, &result, 1); free(result.value); is_int(TOKEN_FAIL_TIMEOUT, status, "can't send due to timeout"); socket_close(client); waitpid(child, NULL, 0); } /* Test a timeout on receiving a token. */ unlink("server-ready"); child = fork(); if (child < 0) sysbail("cannot fork"); else if (child == 0) { server = create_server(); sleep(3); socket_close(server); exit(0); } else { client = create_client(); status = token_recv(client, &flags, &result, 200, 1); is_int(TOKEN_FAIL_TIMEOUT, status, "can't receive due to timeout"); socket_close(client); waitpid(child, NULL, 0); } /* Special test for error handling when sending tokens. */ server = open("/dev/full", O_RDWR); if (server < 0) skip("/dev/full not available"); else { result.value = bmalloc(5); memcpy(result.value, "hello", 5); result.length = 5; status = token_send(server, 3, &result, 0); free(result.value); is_int(TOKEN_FAIL_SOCKET, status, "can't send due to system error"); close(server); } unlink("server-ready"); return 0; }