/* * Report a token error using warn. */ void warn_token(const char *error, int status, OM_uint32 major, OM_uint32 minor) { switch (status) { case TOKEN_OK: warn("error %s", error); break; case TOKEN_FAIL_SYSTEM: syswarn("error %s", error); break; case TOKEN_FAIL_SOCKET: warn("error %s: %s", error, socket_strerror(socket_errno)); break; case TOKEN_FAIL_INVALID: warn("error %s: invalid token format", error); break; case TOKEN_FAIL_LARGE: warn("error %s: token too large", error); break; case TOKEN_FAIL_EOF: warn("error %s: unexpected end of file", error); break; case TOKEN_FAIL_GSSAPI: warn_gssapi(error, major, minor); break; case TOKEN_FAIL_TIMEOUT: warn("error %s: timed out", error); break; default: warn("error %s: unknown error", error); break; } }
/* * Given a service name, imports it and acquires credentials for it, storing * them in the second argument. Returns true on success and false on failure, * logging an error message. * * Normally, you don't want to do this; instead, normally you want to allow * the underlying GSS-API library choose the appropriate credentials from a * keytab for each incoming connection. */ static bool acquire_creds(char *service, gss_cred_id_t *creds) { gss_buffer_desc buffer; gss_name_t name; OM_uint32 major, minor; buffer.value = service; buffer.length = strlen(buffer.value) + 1; major = gss_import_name(&minor, &buffer, GSS_C_NT_USER_NAME, &name); if (major != GSS_S_COMPLETE) { warn_gssapi("while importing name", major, minor); return false; } major = gss_acquire_cred(&minor, name, 0, GSS_C_NULL_OID_SET, GSS_C_ACCEPT, creds, NULL, NULL); if (major != GSS_S_COMPLETE) { warn_gssapi("while acquiring credentials", major, minor); return false; } gss_release_name(&minor, &name); return true; }
/* * Free a client struct, including any resources that it holds. */ void server_free_client(struct client *client) { OM_uint32 major, minor; if (client == NULL) return; if (client->context != GSS_C_NO_CONTEXT) { major = gss_delete_sec_context(&minor, &client->context, NULL); if (major != GSS_S_COMPLETE) warn_gssapi("while deleting context", major, minor); } if (client->fd >= 0) close(client->fd); free(client->user); free(client->hostname); free(client->ipaddress); free(client); }
/* * 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; }