/* * Function: auth_gssapi_destroy * * Purpose: Destroy a GSS-API authentication structure. * * Effects: This function destroys the GSS-API authentication * context, and sends a message to the server instructing it to * invokte gss_process_token() and thereby destroy its corresponding * context. Since the client doesn't really care whether the server * gets this message, no failures are reported. */ static void auth_gssapi_destroy(AUTH *auth) { struct timeval timeout; OM_uint32 gssstat, minor_stat; gss_cred_id_t cred; int callstat; if (AUTH_PRIVATE(auth)->client_handle.length == 0) { PRINTF(("gssapi_destroy: no client_handle, not calling destroy\n")); goto skip_call; } PRINTF(("gssapi_destroy: marshalling new creds\n")); if (!marshall_new_creds(auth, TRUE, &AUTH_PRIVATE(auth)->client_handle)) { PRINTF(("gssapi_destroy: marshall_new_creds failed\n")); goto skip_call; } PRINTF(("gssapi_destroy: calling GSSAPI_DESTROY\n")); timeout.tv_sec = 1; timeout.tv_usec = 0; callstat = clnt_call(AUTH_PRIVATE(auth)->clnt, AUTH_GSSAPI_DESTROY, xdr_void, NULL, xdr_void, NULL, timeout); if (callstat != RPC_SUCCESS) clnt_sperror(AUTH_PRIVATE(auth)->clnt, "gssapi_destroy: GSSAPI_DESTROY failed"); skip_call: PRINTF(("gssapi_destroy: deleting context\n")); gssstat = gss_delete_sec_context(&minor_stat, &AUTH_PRIVATE(auth)->context, NULL); if (gssstat != GSS_S_COMPLETE) AUTH_GSSAPI_DISPLAY_STATUS(("deleting context", gssstat, minor_stat)); if (AUTH_PRIVATE(auth)->def_cred) { cred = GSS_C_NO_CREDENTIAL; gssstat = gss_release_cred(&minor_stat, &cred); if (gssstat != GSS_S_COMPLETE) AUTH_GSSAPI_DISPLAY_STATUS(("deleting default credential", gssstat, minor_stat)); } if (AUTH_PRIVATE(auth)->client_handle.length != 0) gss_release_buffer(&minor_stat, &AUTH_PRIVATE(auth)->client_handle); #if 0 PRINTF(("gssapi_destroy: calling GSSAPI_EXIT\n")); AUTH_PRIVATE(auth)->established = FALSE; callstat = clnt_call(AUTH_PRIVATE(auth)->clnt, AUTH_GSSAPI_EXIT, xdr_void, NULL, xdr_void, NULL, timeout); #endif free(auth->ah_private); free(auth); PRINTF(("gssapi_destroy: done\n")); }
/* * Function: auth_gssapi_create_default * * Purpose: Create a GSS-API style authenticator, with default * options, and return the handle. * * Effects: See design document, section XXX. */ AUTH *auth_gssapi_create_default(CLIENT *clnt, char *service_name) { AUTH *auth; OM_uint32 gssstat, minor_stat; gss_buffer_desc input_name; gss_name_t target_name; input_name.value = service_name; input_name.length = strlen(service_name) + 1; gssstat = gss_import_name(&minor_stat, &input_name, gss_nt_service_name, &target_name); if (gssstat != GSS_S_COMPLETE) { AUTH_GSSAPI_DISPLAY_STATUS(("parsing name", gssstat, minor_stat)); rpc_createerr.cf_stat = RPC_SYSTEMERROR; rpc_createerr.cf_error.re_errno = ENOMEM; return NULL; } auth = auth_gssapi_create(clnt, &gssstat, &minor_stat, GSS_C_NO_CREDENTIAL, target_name, GSS_C_NULL_OID, GSS_C_MUTUAL_FLAG | GSS_C_REPLAY_FLAG, 0, NULL, NULL, NULL); gss_release_name(&minor_stat, &target_name); return auth; }
bool_t auth_gssapi_unseal_seq( gss_ctx_id_t context, gss_buffer_t in_buf, uint32_t *seq_num) { gss_buffer_desc out_buf; OM_uint32 gssstat, minor_stat; uint32_t nl_seq_num; gssstat = gss_unseal(&minor_stat, context, in_buf, &out_buf, NULL, NULL); if (gssstat != GSS_S_COMPLETE) { PRINTF(("gssapi_unseal_seq: failed\n")); AUTH_GSSAPI_DISPLAY_STATUS(("unsealing sequence number", gssstat, minor_stat)); return FALSE; } else if (out_buf.length != sizeof(uint32_t)) { PRINTF(("gssapi_unseal_seq: unseal gave %d bytes\n", (int) out_buf.length)); gss_release_buffer(&minor_stat, &out_buf); return FALSE; } nl_seq_num = *((uint32_t *) out_buf.value); *seq_num = (uint32_t) ntohl(nl_seq_num); gss_release_buffer(&minor_stat, &out_buf); return TRUE; }
/* * Encrypt the serialized arguments from xdr_func applied to xdr_ptr * and write the result to xdrs. */ static bool_t svc_auth_gssapi_wrap( SVCAUTH *auth, XDR *out_xdrs, bool_t (*xdr_func)(), caddr_t xdr_ptr) { OM_uint32 gssstat, minor_stat; if (! SVCAUTH_PRIVATE(auth)->established) { PRINTF(("svc_gssapi_wrap: not established, noop\n")); return (*xdr_func)(out_xdrs, xdr_ptr); } else if (! auth_gssapi_wrap_data(&gssstat, &minor_stat, SVCAUTH_PRIVATE(auth)->context, SVCAUTH_PRIVATE(auth)->seq_num, out_xdrs, xdr_func, xdr_ptr)) { if (gssstat != GSS_S_COMPLETE) AUTH_GSSAPI_DISPLAY_STATUS(("encrypting function arguments", gssstat, minor_stat)); return FALSE; } else return TRUE; }
static bool_t svc_auth_gssapi_unwrap( SVCAUTH *auth, XDR *in_xdrs, bool_t (*xdr_func)(), caddr_t xdr_ptr) { svc_auth_gssapi_data *client_data = SVCAUTH_PRIVATE(auth); OM_uint32 gssstat, minor_stat; if (! client_data->established) { PRINTF(("svc_gssapi_unwrap: not established, noop\n")); return (*xdr_func)(in_xdrs, (auth_gssapi_init_arg *)(void *) xdr_ptr); } else if (! auth_gssapi_unwrap_data(&gssstat, &minor_stat, client_data->context, client_data->seq_num-1, in_xdrs, xdr_func, xdr_ptr)) { if (gssstat != GSS_S_COMPLETE) AUTH_GSSAPI_DISPLAY_STATUS(("decrypting function arguments", gssstat, minor_stat)); return FALSE; } else return TRUE; }
bool_t auth_gssapi_seal_seq( gss_ctx_id_t context, uint32_t seq_num, gss_buffer_t out_buf) { gss_buffer_desc in_buf; OM_uint32 gssstat, minor_stat; uint32_t nl_seq_num; nl_seq_num = htonl(seq_num); in_buf.length = sizeof(uint32_t); in_buf.value = (char *) &nl_seq_num; gssstat = gss_seal(&minor_stat, context, 0, GSS_C_QOP_DEFAULT, &in_buf, NULL, out_buf); if (gssstat != GSS_S_COMPLETE) { PRINTF(("gssapi_seal_seq: failed\n")); AUTH_GSSAPI_DISPLAY_STATUS(("sealing sequence number", gssstat, minor_stat)); return FALSE; } return TRUE; }
/* * Function: svcauth_gssapi_set_names * * Purpose: Sets the list of service names for which incoming * authentication requests should be honored. * * See functional specifications. */ bool_t svcauth_gssapi_set_names( auth_gssapi_name *names, int num) { OM_uint32 gssstat, minor_stat; gss_buffer_desc in_buf; int i; if (num == 0) for (; names[num].name != NULL; num++) ; server_creds_list = NULL; server_name_list = NULL; server_creds_list = (gss_cred_id_t *) malloc(num*sizeof(gss_cred_id_t)); if (server_creds_list == NULL) goto fail; server_name_list = (gss_name_t *) malloc(num*sizeof(gss_name_t)); if (server_name_list == NULL) goto fail; for (i = 0; i < num; i++) { server_name_list[i] = 0; server_creds_list[i] = 0; } server_creds_count = num; for (i = 0; i < num; i++) { in_buf.value = names[i].name; in_buf.length = strlen(in_buf.value) + 1; PRINTF(("svcauth_gssapi_set_names: importing %s\n", names[i].name)); gssstat = gss_import_name(&minor_stat, &in_buf, names[i].type, &server_name_list[i]); if (gssstat != GSS_S_COMPLETE) { AUTH_GSSAPI_DISPLAY_STATUS(("importing name", gssstat, minor_stat)); goto fail; } gssstat = gss_acquire_cred(&minor_stat, server_name_list[i], 0, GSS_C_NULL_OID_SET, GSS_C_ACCEPT, &server_creds_list[i], NULL, NULL); if (gssstat != GSS_S_COMPLETE) { AUTH_GSSAPI_DISPLAY_STATUS(("acquiring credentials", gssstat, minor_stat)); goto fail; } } return TRUE; fail: svcauth_gssapi_unset_names(); return FALSE; }
/* * Function: destroy_client * * Purpose: destroys a client entry and removes it from the database * * Arguments: * * client_data (r) the client to be destroyed * * Effects: * * client_data->context is deleted with gss_delete_sec_context. * client_data's entry in the database is destroyed. client_data is * freed. */ static void destroy_client(svc_auth_gssapi_data *client_data) { OM_uint32 gssstat, minor_stat; gss_buffer_desc out_buf; client_list *c, *c2; PRINTF(("destroy_client: destroying client_data\n")); L_PRINTF(2, ("destroy_client: client_data = %p\n", (void *) client_data)); #ifdef DEBUG_GSSAPI if (svc_debug_gssapi >= 3) dump_db("before frees"); #endif /* destroy client struct even if error occurs */ gssstat = gss_delete_sec_context(&minor_stat, &client_data->context, &out_buf); if (gssstat != GSS_S_COMPLETE) AUTH_GSSAPI_DISPLAY_STATUS(("deleting context", gssstat, minor_stat)); gss_release_buffer(&minor_stat, &out_buf); gss_release_name(&minor_stat, &client_data->client_name); if (client_data->prev_verf.length != 0) gss_release_buffer(&minor_stat, &client_data->prev_verf); if (clients == NULL) { PRINTF(("destroy_client: called on empty database\n")); abort(); } else if (clients->client == client_data) { c = clients; clients = clients->next; free(c); } else { c2 = clients; c = clients->next; while (c) { if (c->client == client_data) { c2->next = c->next; free(c); goto done; } else { c2 = c; c = c->next; } } PRINTF(("destroy_client: client_handle delete failed\n")); abort(); } done: L_PRINTF(2, ("destroy_client: client %d destroyed\n", client_data->key)); free(client_data); #if 0 /*ifdef PURIFY*/ purify_watch_n(client_data, sizeof(*client_data), "rw"); #endif }
enum auth_stat gssrpc__svcauth_gssapi( register struct svc_req *rqst, register struct rpc_msg *msg, bool_t *no_dispatch) { XDR xdrs; auth_gssapi_creds creds; auth_gssapi_init_arg call_arg; auth_gssapi_init_res call_res; gss_buffer_desc output_token, in_buf, out_buf; gss_cred_id_t server_creds; struct gss_channel_bindings_struct bindings, *bindp; OM_uint32 gssstat, minor_stat, time_rec; struct opaque_auth *cred, *verf; svc_auth_gssapi_data *client_data; int i; enum auth_stat ret; OM_uint32 ret_flags; uint32_t seq_num; PRINTF(("svcauth_gssapi: starting\n")); /* clean up expired entries */ clean_client(); /* use AUTH_NONE until there is a client_handle */ rqst->rq_xprt->xp_auth = &svc_auth_none; memset((char *) &call_res, 0, sizeof(call_res)); creds.client_handle.length = 0; creds.client_handle.value = NULL; cred = &msg->rm_call.cb_cred; verf = &msg->rm_call.cb_verf; if (cred->oa_length == 0) { PRINTF(("svcauth_gssapi: empty creds, failing\n")); LOG_MISCERR("empty client credentials"); ret = AUTH_BADCRED; goto error; } PRINTF(("svcauth_gssapi: decoding credentials\n")); xdrmem_create(&xdrs, cred->oa_base, cred->oa_length, XDR_DECODE); memset((char *) &creds, 0, sizeof(creds)); if (! xdr_authgssapi_creds(&xdrs, &creds)) { PRINTF(("svcauth_gssapi: failed decoding creds\n")); LOG_MISCERR("protocol error in client credentials"); xdr_free(xdr_authgssapi_creds, &creds); XDR_DESTROY(&xdrs); ret = AUTH_BADCRED; goto error; } XDR_DESTROY(&xdrs); PRINTF(("svcauth_gssapi: got credentials, version %d, client_handle len %d\n", creds.version, (int) creds.client_handle.length)); if (creds.version != 2) { PRINTF(("svcauth_gssapi: bad credential version\n")); LOG_MISCERR("unsupported client credentials version"); ret = AUTH_BADCRED; goto error; } #ifdef DEBUG_GSSAPI if (svc_debug_gssapi) { if (creds.auth_msg && rqst->rq_proc == AUTH_GSSAPI_EXIT) { PRINTF(("svcauth_gssapi: GSSAPI_EXIT, cleaning up\n")); svc_sendreply(rqst->rq_xprt, xdr_void, NULL); xdr_free(xdr_authgssapi_creds, &creds); cleanup(); exit(0); } } #endif /* * If this is an auth_msg and proc is GSSAPI_INIT, then create a * client handle for this client. Otherwise, look up the * existing handle. */ if (creds.auth_msg && rqst->rq_proc == AUTH_GSSAPI_INIT) { if (creds.client_handle.length != 0) { PRINTF(("svcauth_gssapi: non-empty handle on GSSAPI_INIT\n")); LOG_MISCERR("protocol error in client handle"); ret = AUTH_FAILED; goto error; } PRINTF(("svcauth_gssapi: GSSAPI_INIT, creating client.\n")); client_data = create_client(); if (client_data == NULL) { PRINTF(("svcauth_gssapi: create_client failed\n")); LOG_MISCERR("internal error creating client record"); ret = AUTH_FAILED; goto error; } } else { if (creds.client_handle.length == 0) { PRINTF(("svcauth_gssapi: expected non-empty creds\n")); LOG_MISCERR("protocol error in client credentials"); ret = AUTH_FAILED; goto error; } PRINTF(("svcauth_gssapi: incoming client_handle %d, len %d\n", *((uint32_t *) creds.client_handle.value), (int) creds.client_handle.length)); client_data = get_client(&creds.client_handle); if (client_data == NULL) { PRINTF(("svcauth_gssapi: client_handle lookup failed\n")); LOG_MISCERR("invalid client handle received"); ret = AUTH_BADCRED; goto error; } PRINTF(("svcauth_gssapi: client_handle lookup succeeded\n")); } /* any response we send will use client_handle, so set it now */ call_res.client_handle.length = sizeof(client_data->key); call_res.client_handle.value = (char *) &client_data->key; /* mark this call as using AUTH_GSSAPI via client_data's SVCAUTH */ rqst->rq_xprt->xp_auth = &client_data->svcauth; if (client_data->established == FALSE) { PRINTF(("svcauth_gssapi: context is not established\n")); if (creds.auth_msg == FALSE) { PRINTF(("svcauth_gssapi: expected auth_msg TRUE\n")); LOG_MISCERR("protocol error on incomplete connection"); ret = AUTH_REJECTEDCRED; goto error; } /* * If the context is not established, then only GSSAPI_INIT * and _CONTINUE requests are valid. */ if (rqst->rq_proc != AUTH_GSSAPI_INIT && rqst->rq_proc != AUTH_GSSAPI_CONTINUE_INIT) { PRINTF(("svcauth_gssapi: unacceptable procedure %d\n", rqst->rq_proc)); LOG_MISCERR("protocol error on incomplete connection"); ret = AUTH_FAILED; goto error; } /* call is for us, deserialize arguments */ memset(&call_arg, 0, sizeof(call_arg)); if (! svc_getargs(rqst->rq_xprt, xdr_authgssapi_init_arg, &call_arg)) { PRINTF(("svcauth_gssapi: cannot decode args\n")); LOG_MISCERR("protocol error in procedure arguments"); ret = AUTH_BADCRED; goto error; } /* * Process the call arg version number. * * Set the krb5_gss backwards-compatibility mode based on client * version. This controls whether the AP_REP message is * encrypted with the session key (version 2+, correct) or the * session subkey (version 1, incorrect). This function can * never fail, so we don't bother checking its return value. */ switch (call_arg.version) { case 1: case 2: LOG_MISCERR("Warning: Accepted old RPC protocol request"); call_res.version = 1; break; case 3: case 4: /* 3 and 4 are essentially the same, don't bother warning */ call_res.version = call_arg.version; break; default: PRINTF(("svcauth_gssapi: bad GSSAPI_INIT version\n")); LOG_MISCERR("unsupported GSSAPI_INIT version"); ret = AUTH_BADCRED; goto error; } #ifdef GSS_BACKWARD_HACK krb5_gss_set_backward_mode(&minor_stat, call_arg.version == 1); #endif if (call_arg.version >= 3) { memset(&bindings, 0, sizeof(bindings)); bindings.application_data.length = 0; bindings.initiator_addrtype = GSS_C_AF_INET; bindings.initiator_address.length = 4; bindings.initiator_address.value = &svc_getcaller(rqst->rq_xprt)->sin_addr.s_addr; if (rqst->rq_xprt->xp_laddrlen > 0) { bindings.acceptor_addrtype = GSS_C_AF_INET; bindings.acceptor_address.length = 4; bindings.acceptor_address.value = &rqst->rq_xprt->xp_laddr.sin_addr.s_addr; } else { LOG_MISCERR("cannot get local address"); ret = AUTH_FAILED; goto error; } bindp = &bindings; } else { bindp = GSS_C_NO_CHANNEL_BINDINGS; } /* * If the client's server_creds is already set, use it. * Otherwise, try each credential in server_creds_list until * one of them succeedes, then set the client server_creds * to that. If all fail, the client's server_creds isn't * set (which is fine, because the client will be gc'ed * anyway). * * If accept_sec_context returns something other than * success and GSS_S_FAILURE, then assume different * credentials won't help and stop looping. * * Note that there are really two cases here: (1) the client * has a server_creds already, and (2) it does not. They * are both written in the same loop so that there is only * one textual call to gss_accept_sec_context; in fact, in * case (1), the loop is executed exactly once. */ for (i = 0; i < server_creds_count; i++) { if (client_data->server_creds != NULL) { PRINTF(("svcauth_gssapi: using's clients server_creds\n")); server_creds = client_data->server_creds; } else { PRINTF(("svcauth_gssapi: trying creds %d\n", i)); server_creds = server_creds_list[i]; } /* Free previous output_token from loop */ if(i != 0) gss_release_buffer(&minor_stat, &output_token); call_res.gss_major = gss_accept_sec_context(&call_res.gss_minor, &client_data->context, server_creds, &call_arg.token, bindp, &client_data->client_name, NULL, &output_token, &ret_flags, &time_rec, NULL); if (server_creds == client_data->server_creds) break; PRINTF(("accept_sec_context returned 0x%x 0x%x wrong-princ=%#x\n", call_res.gss_major, call_res.gss_minor, (int) KRB5KRB_AP_WRONG_PRINC)); if (call_res.gss_major == GSS_S_COMPLETE || call_res.gss_major == GSS_S_CONTINUE_NEEDED) { /* server_creds was right, set it! */ PRINTF(("svcauth_gssapi: creds are correct, storing\n")); client_data->server_creds = server_creds; client_data->server_name = server_name_list[i]; break; } else if (call_res.gss_major != GSS_S_FAILURE #ifdef GSSAPI_KRB5 /* * hard-coded because there is no other way * to prevent all GSS_S_FAILURES from * returning a "wrong principal in request" * error */ || ((krb5_error_code) call_res.gss_minor != (krb5_error_code) KRB5KRB_AP_WRONG_PRINC) #endif ) { break; } } gssstat = call_res.gss_major; minor_stat = call_res.gss_minor; /* done with call args */ xdr_free(xdr_authgssapi_init_arg, &call_arg); PRINTF(("svcauth_gssapi: accept_sec_context returned %#x %#x\n", call_res.gss_major, call_res.gss_minor)); if (call_res.gss_major != GSS_S_COMPLETE && call_res.gss_major != GSS_S_CONTINUE_NEEDED) { AUTH_GSSAPI_DISPLAY_STATUS(("accepting context", call_res.gss_major, call_res.gss_minor)); if (log_badauth != NULL) (*log_badauth)(call_res.gss_major, call_res.gss_minor, &rqst->rq_xprt->xp_raddr, log_badauth_data); gss_release_buffer(&minor_stat, &output_token); svc_sendreply(rqst->rq_xprt, xdr_authgssapi_init_res, (caddr_t) &call_res); *no_dispatch = TRUE; ret = AUTH_OK; goto error; } if (output_token.length != 0) { PRINTF(("svcauth_gssapi: got new output token\n")); GSS_COPY_BUFFER(call_res.token, output_token); } if (gssstat == GSS_S_COMPLETE) { client_data->seq_num = rand(); client_expire(client_data, (time_rec == GSS_C_INDEFINITE ? INDEF_EXPIRE : time_rec) + time(0)); PRINTF(("svcauth_gssapi: context established, isn %d\n", client_data->seq_num)); if (auth_gssapi_seal_seq(client_data->context, client_data->seq_num, &call_res.signed_isn) == FALSE) { ret = AUTH_FAILED; LOG_MISCERR("internal error sealing sequence number"); gss_release_buffer(&minor_stat, &output_token); goto error; } } PRINTF(("svcauth_gssapi: sending reply\n")); svc_sendreply(rqst->rq_xprt, xdr_authgssapi_init_res, (caddr_t) &call_res); *no_dispatch = TRUE; /* * If appropriate, set established to TRUE *after* sending * response (otherwise, the client will receive the final * token encrypted) */ if (gssstat == GSS_S_COMPLETE) { gss_release_buffer(&minor_stat, &call_res.signed_isn); client_data->established = TRUE; } gss_release_buffer(&minor_stat, &output_token); } else { PRINTF(("svcauth_gssapi: context is established\n")); /* check the verifier */ PRINTF(("svcauth_gssapi: checking verifier, len %d\n", verf->oa_length)); in_buf.length = verf->oa_length; in_buf.value = verf->oa_base; if (auth_gssapi_unseal_seq(client_data->context, &in_buf, &seq_num) == FALSE) { ret = AUTH_BADVERF; LOG_MISCERR("internal error unsealing sequence number"); goto error; } if (seq_num != client_data->seq_num + 1) { PRINTF(("svcauth_gssapi: expected isn %d, got %d\n", client_data->seq_num + 1, seq_num)); if (log_badverf != NULL) (*log_badverf)(client_data->client_name, client_data->server_name, rqst, msg, log_badverf_data); ret = AUTH_REJECTEDVERF; goto error; } client_data->seq_num++; PRINTF(("svcauth_gssapi: seq_num %d okay\n", seq_num)); /* free previous response verifier, if any */ if (client_data->prev_verf.length != 0) { gss_release_buffer(&minor_stat, &client_data->prev_verf); client_data->prev_verf.length = 0; } /* prepare response verifier */ seq_num = client_data->seq_num + 1; if (auth_gssapi_seal_seq(client_data->context, seq_num, &out_buf) == FALSE) { ret = AUTH_FAILED; LOG_MISCERR("internal error sealing sequence number"); goto error; } client_data->seq_num++; PRINTF(("svcauth_gssapi; response seq_num %d\n", seq_num)); rqst->rq_xprt->xp_verf.oa_flavor = AUTH_GSSAPI; rqst->rq_xprt->xp_verf.oa_base = out_buf.value; rqst->rq_xprt->xp_verf.oa_length = out_buf.length; /* save verifier so it can be freed next time */ client_data->prev_verf.value = out_buf.value; client_data->prev_verf.length = out_buf.length; /* * Message is authentic. If auth_msg if true, process the * call; otherwise, return AUTH_OK so it will be dispatched * to the application server. */ if (creds.auth_msg == TRUE) { /* * If process_token fails, then the token probably came * from an attacker. No response (error or otherwise) * should be returned to the client, since it won't be * accepting one. */ switch (rqst->rq_proc) { case AUTH_GSSAPI_MSG: PRINTF(("svcauth_gssapi: GSSAPI_MSG, getting args\n")); memset(&call_arg, 0, sizeof(call_arg)); if (! svc_getargs(rqst->rq_xprt, xdr_authgssapi_init_arg, &call_arg)) { PRINTF(("svcauth_gssapi: cannot decode args\n")); LOG_MISCERR("protocol error in call arguments"); xdr_free(xdr_authgssapi_init_arg, &call_arg); ret = AUTH_BADCRED; goto error; } PRINTF(("svcauth_gssapi: processing token\n")); gssstat = gss_process_context_token(&minor_stat, client_data->context, &call_arg.token); /* done with call args */ xdr_free(xdr_authgssapi_init_arg, &call_arg); if (gssstat != GSS_S_COMPLETE) { AUTH_GSSAPI_DISPLAY_STATUS(("processing token", gssstat, minor_stat)); ret = AUTH_FAILED; goto error; } svc_sendreply(rqst->rq_xprt, xdr_void, NULL); *no_dispatch = TRUE; break; case AUTH_GSSAPI_DESTROY: PRINTF(("svcauth_gssapi: GSSAPI_DESTROY\n")); PRINTF(("svcauth_gssapi: sending reply\n")); svc_sendreply(rqst->rq_xprt, xdr_void, NULL); *no_dispatch = TRUE; destroy_client(client_data); rqst->rq_xprt->xp_auth = NULL; break; default: PRINTF(("svcauth_gssapi: unacceptable procedure %d\n", rqst->rq_proc)); LOG_MISCERR("invalid call procedure number"); ret = AUTH_FAILED; goto error; } } else { /* set credentials for app server; comment in svc.c */ /* seems to imply this is incorrect, but I don't see */ /* any problem with it... */ rqst->rq_clntcred = (char *)client_data->client_name; rqst->rq_svccred = (char *)client_data->context; } } if (creds.client_handle.length != 0) { PRINTF(("svcauth_gssapi: freeing client_handle len %d\n", (int) creds.client_handle.length)); xdr_free(xdr_authgssapi_creds, &creds); } PRINTF(("\n")); return AUTH_OK; error: if (creds.client_handle.length != 0) { PRINTF(("svcauth_gssapi: freeing client_handle len %d\n", (int) creds.client_handle.length)); xdr_free(xdr_authgssapi_creds, &creds); } PRINTF(("\n")); return ret; }
/* * Function: auth_gssapi_create * * Purpose: Create a GSS-API style authenticator, with all the * options, and return the handle. * * Effects: See design document, section XXX. */ AUTH *auth_gssapi_create( CLIENT *clnt, OM_uint32 *gssstat, OM_uint32 *minor_stat, gss_cred_id_t claimant_cred_handle, gss_name_t target_name, gss_OID mech_type, OM_uint32 req_flags, OM_uint32 time_req, gss_OID *actual_mech_type, OM_uint32 *ret_flags, OM_uint32 *time_rec) { AUTH *auth, *save_auth; struct auth_gssapi_data *pdata; struct gss_channel_bindings_struct bindings, *bindp; struct sockaddr_in laddr, raddr; enum clnt_stat callstat; struct timeval timeout; int bindings_failed; rpcproc_t init_func; auth_gssapi_init_arg call_arg; auth_gssapi_init_res call_res; gss_buffer_desc *input_token, isn_buf; memset(&rpc_createerr, 0, sizeof(rpc_createerr)); /* this timeout is only used if clnt_control(clnt, CLSET_TIMEOUT) */ /* has not already been called.. therefore, we can just pick */ /* something reasonable-sounding.. */ timeout.tv_sec = 30; timeout.tv_usec = 0; auth = NULL; pdata = NULL; /* don't assume the caller will want to change clnt->cl_auth */ save_auth = clnt->cl_auth; auth = (AUTH *) malloc(sizeof(*auth)); pdata = (struct auth_gssapi_data *) malloc(sizeof(*pdata)); if (auth == NULL || pdata == NULL) { /* They needn't both have failed; clean up. */ free(auth); free(pdata); auth = NULL; pdata = NULL; rpc_createerr.cf_stat = RPC_SYSTEMERROR; rpc_createerr.cf_error.re_errno = ENOMEM; goto cleanup; } memset((char *) auth, 0, sizeof(*auth)); memset((char *) pdata, 0, sizeof(*pdata)); auth->ah_ops = &auth_gssapi_ops; auth->ah_private = (caddr_t) pdata; /* initial creds are auth_msg TRUE and no handle */ marshall_new_creds(auth, TRUE, NULL); /* initial verifier is empty */ auth->ah_verf.oa_flavor = AUTH_GSSAPI; auth->ah_verf.oa_base = NULL; auth->ah_verf.oa_length = 0; AUTH_PRIVATE(auth)->established = FALSE; AUTH_PRIVATE(auth)->clnt = clnt; AUTH_PRIVATE(auth)->def_cred = (claimant_cred_handle == GSS_C_NO_CREDENTIAL); clnt->cl_auth = auth; /* start by trying latest version */ call_arg.version = 4; bindings_failed = 0; try_new_version: /* set state for initial call to init_sec_context */ input_token = GSS_C_NO_BUFFER; AUTH_PRIVATE(auth)->context = GSS_C_NO_CONTEXT; init_func = AUTH_GSSAPI_INIT; #ifdef GSSAPI_KRB5 /* * OV servers up to version 3 used the old mech id. Beta 7 * servers used version 3 with the new mech id; however, the beta * 7 gss-api accept_sec_context accepts either mech id. Thus, if * any server rejects version 4, we fall back to version 3 with * the old mech id; for the OV server it will be right, and for * the beta 7 server it will be accepted. Not ideal, but it * works. */ if (call_arg.version < 4 && (mech_type == gss_mech_krb5 || mech_type == GSS_C_NULL_OID)) mech_type = (gss_OID) gss_mech_krb5_old; #endif if (!bindings_failed && call_arg.version >= 3) { if (clnt_control(clnt, CLGET_LOCAL_ADDR, &laddr) == FALSE) { PRINTF(("gssapi_create: CLGET_LOCAL_ADDR failed")); goto cleanup; } if (clnt_control(clnt, CLGET_SERVER_ADDR, &raddr) == FALSE) { PRINTF(("gssapi_create: CLGET_SERVER_ADDR failed")); goto cleanup; } memset(&bindings, 0, sizeof(bindings)); bindings.application_data.length = 0; bindings.initiator_addrtype = GSS_C_AF_INET; bindings.initiator_address.length = 4; bindings.initiator_address.value = &laddr.sin_addr.s_addr; bindings.acceptor_addrtype = GSS_C_AF_INET; bindings.acceptor_address.length = 4; bindings.acceptor_address.value = &raddr.sin_addr.s_addr; bindp = &bindings; } else { bindp = NULL; } memset((char *) &call_res, 0, sizeof(call_res)); next_token: *gssstat = gss_init_sec_context(minor_stat, claimant_cred_handle, &AUTH_PRIVATE(auth)->context, target_name, mech_type, req_flags, time_req, bindp, input_token, actual_mech_type, &call_arg.token, ret_flags, time_rec); if (*gssstat != GSS_S_COMPLETE && *gssstat != GSS_S_CONTINUE_NEEDED) { AUTH_GSSAPI_DISPLAY_STATUS(("initializing context", *gssstat, *minor_stat)); goto cleanup; } /* if we got a token, pass it on */ if (call_arg.token.length != 0) { /* * sanity check: if we received a signed isn in the last * response then there *cannot* be another token to send */ if (call_res.signed_isn.length != 0) { PRINTF(("gssapi_create: unexpected token from init_sec\n")); goto cleanup; } PRINTF(("gssapi_create: calling GSSAPI_INIT (%d)\n", init_func)); memset((char *) &call_res, 0, sizeof(call_res)); callstat = clnt_call(clnt, init_func, xdr_authgssapi_init_arg, &call_arg, xdr_authgssapi_init_res, &call_res, timeout); gss_release_buffer(minor_stat, &call_arg.token); if (callstat != RPC_SUCCESS) { struct rpc_err err; clnt_geterr(clnt, &err); if (callstat == RPC_AUTHERROR && (err.re_why == AUTH_BADCRED || err.re_why == AUTH_FAILED) && call_arg.version >= 1) { L_PRINTF(1, ("call_arg protocol version %d rejected, trying %d.\n", call_arg.version, call_arg.version-1)); call_arg.version--; goto try_new_version; } else { PRINTF(("gssapi_create: GSSAPI_INIT (%d) failed, stat %d\n", init_func, callstat)); } goto cleanup; } else if (call_res.version != call_arg.version && !(call_arg.version == 2 && call_res.version == 1)) { /* * The Secure 1.1 servers always respond with version * 1. Thus, if we just tried a version >=3, fall all * the way back to version 1 since that is all they * understand */ if (call_arg.version > 2 && call_res.version == 1) { L_PRINTF(1, ("Talking to Secure 1.1 server, using version 1.\n")); call_arg.version = 1; goto try_new_version; } PRINTF(("gssapi_create: invalid call_res vers %d\n", call_res.version)); goto cleanup; } else if (call_res.gss_major != GSS_S_COMPLETE) { AUTH_GSSAPI_DISPLAY_STATUS(("in response from server", call_res.gss_major, call_res.gss_minor)); goto cleanup; } PRINTF(("gssapi_create: GSSAPI_INIT (%d) succeeded\n", init_func)); init_func = AUTH_GSSAPI_CONTINUE_INIT; /* check for client_handle */ if (AUTH_PRIVATE(auth)->client_handle.length == 0) { if (call_res.client_handle.length == 0) { PRINTF(("gssapi_create: expected client_handle\n")); goto cleanup; } else { PRINTF(("gssapi_create: got client_handle %d\n", *((uint32_t *)call_res.client_handle.value))); GSS_DUP_BUFFER(AUTH_PRIVATE(auth)->client_handle, call_res.client_handle); /* auth_msg is TRUE; there may be more tokens */ marshall_new_creds(auth, TRUE, &AUTH_PRIVATE(auth)->client_handle); } } else if (!GSS_BUFFERS_EQUAL(AUTH_PRIVATE(auth)->client_handle, call_res.client_handle)) { PRINTF(("gssapi_create: got different client_handle\n")); goto cleanup; } /* check for token */ if (call_res.token.length==0 && *gssstat==GSS_S_CONTINUE_NEEDED) { PRINTF(("gssapi_create: expected token\n")); goto cleanup; } else if (call_res.token.length != 0) { if (*gssstat == GSS_S_COMPLETE) { PRINTF(("gssapi_create: got unexpected token\n")); goto cleanup; } else { /* assumes call_res is safe until init_sec_context */ input_token = &call_res.token; PRINTF(("gssapi_create: got new token\n")); } } } /* check for isn */ if (*gssstat == GSS_S_COMPLETE) { if (call_res.signed_isn.length == 0) { PRINTF(("gssapi_created: expected signed isn\n")); goto cleanup; } else { PRINTF(("gssapi_create: processing signed isn\n")); /* don't check conf (integ only) or qop (accpet default) */ *gssstat = gss_unseal(minor_stat, AUTH_PRIVATE(auth)->context, &call_res.signed_isn, &isn_buf, NULL, NULL); if (*gssstat != GSS_S_COMPLETE) { AUTH_GSSAPI_DISPLAY_STATUS(("unsealing isn", *gssstat, *minor_stat)); goto cleanup; } else if (isn_buf.length != sizeof(uint32_t)) { PRINTF(("gssapi_create: gss_unseal gave %d bytes\n", (int) isn_buf.length)); goto cleanup; } AUTH_PRIVATE(auth)->seq_num = (uint32_t) ntohl(*((uint32_t*)isn_buf.value)); *gssstat = gss_release_buffer(minor_stat, &isn_buf); if (*gssstat != GSS_S_COMPLETE) { AUTH_GSSAPI_DISPLAY_STATUS(("releasing unsealed isn", *gssstat, *minor_stat)); goto cleanup; } PRINTF(("gssapi_create: isn is %d\n", AUTH_PRIVATE(auth)->seq_num)); /* we no longer need these results.. */ xdr_free(xdr_authgssapi_init_res, &call_res); } } else if (call_res.signed_isn.length != 0) { PRINTF(("gssapi_create: got signed isn, can't check yet\n")); } /* results were okay.. continue if necessary */ if (*gssstat == GSS_S_CONTINUE_NEEDED) { PRINTF(("gssapi_create: not done, continuing\n")); goto next_token; } /* * Done! Context is established, we have client_handle and isn. */ AUTH_PRIVATE(auth)->established = TRUE; marshall_new_creds(auth, FALSE, &AUTH_PRIVATE(auth)->client_handle); PRINTF(("gssapi_create: done. client_handle %#x, isn %d\n\n", *((uint32_t *)AUTH_PRIVATE(auth)->client_handle.value), AUTH_PRIVATE(auth)->seq_num)); /* don't assume the caller will want to change clnt->cl_auth */ clnt->cl_auth = save_auth; return auth; /******************************************************************/ cleanup: PRINTF(("gssapi_create: bailing\n\n")); if (auth) { if (AUTH_PRIVATE(auth)) auth_gssapi_destroy(auth); else free(auth); auth = NULL; } /* don't assume the caller will want to change clnt->cl_auth */ clnt->cl_auth = save_auth; if (rpc_createerr.cf_stat == 0) rpc_createerr.cf_stat = RPC_AUTHERROR; return auth; }