/* * note we inherited assumed authority from parent process */ static int lgssc_kr_negotiate(key_serial_t keyid, struct lgss_cred *cred, struct keyring_upcall_param *kup) { struct lgss_nego_data lnd; gss_buffer_desc token = GSS_C_EMPTY_BUFFER; OM_uint32 min_stat; int rc = -1; logmsg(LL_TRACE, "child start on behalf of key %08x: " "cred %p, uid %u, svc %u, nid %llx, uids: %u:%u/%u:%u\n", keyid, cred, cred->lc_uid, cred->lc_tgt_svc, cred->lc_tgt_nid, kup->kup_uid, kup->kup_gid, kup->kup_fsuid, kup->kup_fsgid); if (lgss_get_service_str(&g_service, kup->kup_svc, kup->kup_nid)) { logmsg(LL_ERR, "key %08x: failed to construct service " "string\n", keyid); error_kernel_key(keyid, -EACCES, 0); goto out_cred; } if (lgss_using_cred(cred)) { logmsg(LL_ERR, "key %08x: can't using cred\n", keyid); error_kernel_key(keyid, -EACCES, 0); goto out_cred; } if (lgssc_init_nego_data(&lnd, kup, cred->lc_mech->lmt_mech_n)) { logmsg(LL_ERR, "key %08x: failed to initialize " "negotiation data\n", keyid); error_kernel_key(keyid, lnd.lnd_rpc_err, lnd.lnd_gss_err); goto out_cred; } rc = lgssc_negotiation(&lnd); if (rc) { logmsg(LL_ERR, "key %08x: failed to negotiation\n", keyid); error_kernel_key(keyid, lnd.lnd_rpc_err, lnd.lnd_gss_err); goto out; } rc = serialize_context_for_kernel(lnd.lnd_ctx, &token, lnd.lnd_mech); if (rc) { logmsg(LL_ERR, "key %08x: failed to export context\n", keyid); error_kernel_key(keyid, rc, lnd.lnd_gss_err); goto out; } rc = update_kernel_key(keyid, &lnd, &token); if (rc) goto out; rc = 0; logmsg(LL_INFO, "key %08x for user %u is updated OK!\n", keyid, kup->kup_uid); out: if (token.length != 0) gss_release_buffer(&min_stat, &token); lgssc_fini_nego_data(&lnd); out_cred: lgss_release_cred(cred); return rc; }
/* * this code uses the userland rpcsec gss library to create a krb5 * context on behalf of the kernel */ static void process_krb5_upcall(struct clnt_info *clp, uid_t uid, int fd, char *tgtname, char *service) { CLIENT *rpc_clnt = NULL; AUTH *auth = NULL; struct authgss_private_data pd; gss_buffer_desc token; int err, downcall_err = -EACCES; OM_uint32 maj_stat, min_stat, lifetime_rec; pid_t pid, childpid = -1; gss_name_t gacceptor = GSS_C_NO_NAME; gss_OID mech; gss_buffer_desc acceptor = {0}; token.length = 0; token.value = NULL; memset(&pd, 0, sizeof(struct authgss_private_data)); /* * If "service" is specified, then the kernel is indicating that * we must use machine credentials for this request. (Regardless * of the uid value or the setting of root_uses_machine_creds.) * If the service value is "*", then any service name can be used. * Otherwise, it specifies the service name that should be used. * (For now, the values of service will only be "*" or "nfs".) * * Restricting gssd to use "nfs" service name is needed for when * the NFS server is doing a callback to the NFS client. In this * case, the NFS server has to authenticate itself as "nfs" -- * even if there are other service keys such as "host" or "root" * in the keytab. * * Another case when the kernel may specify the service attribute * is when gssd is being asked to create the context for a * SETCLIENT_ID operation. In this case, machine credentials * must be used for the authentication. However, the service name * used for this case is not important. * */ if (uid != 0 || (uid == 0 && root_uses_machine_creds == 0 && service == NULL)) { /* already running as uid 0 */ if (uid == 0) goto no_fork; pid = fork(); switch(pid) { case 0: /* Child: fall through to rest of function */ childpid = getpid(); unsetenv("KRB5CCNAME"); printerr(2, "CHILD forked pid %d \n", childpid); break; case -1: /* fork() failed! */ printerr(0, "WARNING: unable to fork() to handle" "upcall: %s\n", strerror(errno)); return; default: /* Parent: just wait on child to exit and return */ do { pid = wait(&err); } while(pid == -1 && errno != -ECHILD); if (WIFSIGNALED(err)) printerr(0, "WARNING: forked child was killed" "with signal %d\n", WTERMSIG(err)); return; } no_fork: auth = krb5_not_machine_creds(clp, uid, tgtname, &downcall_err, &err, &rpc_clnt); if (err) goto out_return_error; } if (auth == NULL) { if (uid == 0 && (root_uses_machine_creds == 1 || service != NULL)) { auth = krb5_use_machine_creds(clp, uid, tgtname, service, &rpc_clnt); if (auth == NULL) goto out_return_error; } else { /* krb5_not_machine_creds logs the error */ goto out_return_error; } } if (!authgss_get_private_data(auth, &pd)) { printerr(1, "WARNING: Failed to obtain authentication " "data for user with uid %d for server %s\n", uid, clp->servername); goto out_return_error; } /* Grab the context lifetime and acceptor name out of the ctx. */ maj_stat = gss_inquire_context(&min_stat, pd.pd_ctx, NULL, &gacceptor, &lifetime_rec, &mech, NULL, NULL, NULL); if (maj_stat != GSS_S_COMPLETE) { printerr(1, "WARNING: Failed to inquire context " "maj_stat (0x%x)\n", maj_stat); lifetime_rec = 0; } else { get_hostbased_client_buffer(gacceptor, mech, &acceptor); gss_release_name(&min_stat, &gacceptor); } /* * The serialization can mean turning pd.pd_ctx into a lucid context. If * that happens then the pd.pd_ctx will be unusable, so we must never * try to use it after this point. */ if (serialize_context_for_kernel(&pd.pd_ctx, &token, &krb5oid, NULL)) { printerr(1, "WARNING: Failed to serialize krb5 context for " "user with uid %d for server %s\n", uid, clp->servername); goto out_return_error; } do_downcall(fd, uid, &pd, &token, lifetime_rec, &acceptor); out: gss_release_buffer(&min_stat, &acceptor); if (token.value) free(token.value); #ifdef HAVE_AUTHGSS_FREE_PRIVATE_DATA if (pd.pd_ctx_hndl.length != 0 || pd.pd_ctx != 0) authgss_free_private_data(&pd); #endif if (auth) AUTH_DESTROY(auth); if (rpc_clnt) clnt_destroy(rpc_clnt); pid = getpid(); if (pid == childpid) exit(0); else return; out_return_error: do_error_downcall(fd, uid, downcall_err); goto out; }