// SPDX-License-Identifier: BSD-3-Clause /* * linux/net/sunrpc/auth_gss/auth_gss.c * * RPCSEC_GSS client authentication. * * Copyright (c) 2000 The Regents of the University of Michigan. * All rights reserved. * * Dug Song <dugsong@monkey.org> * Andy Adamson <andros@umich.edu>
*/
/* * This compile-time check verifies that we will not exceed the * slack space allotted by the client and server auth_gss code * before they call gss_wrap().
*/ #define GSS_KRB5_MAX_SLACK_NEEDED \
(GSS_KRB5_TOK_HDR_LEN /* gss token header */ \
+ GSS_KRB5_MAX_CKSUM_LEN /* gss token checksum */ \
+ GSS_KRB5_MAX_BLOCKSIZE /* confounder */ \
+ GSS_KRB5_MAX_BLOCKSIZE /* possible padding */ \
+ GSS_KRB5_TOK_HDR_LEN /* encrypted hdr in v2 token */ \
+ GSS_KRB5_MAX_CKSUM_LEN /* encryption hmac */ \
+ XDR_UNIT * 2 /* RPC verifier */ \
+ GSS_KRB5_TOK_HDR_LEN \
+ GSS_KRB5_MAX_CKSUM_LEN)
#define GSS_CRED_SLACK (RPC_MAX_AUTH_SIZE * 2) /* length of a krb5 verifier (48), plus data added before arguments when
* using integrity (two 4-byte integers): */ #define GSS_VERF_SLACK 100
struct gss_auth { struct kref kref; struct hlist_node hash; struct rpc_auth rpc_auth; struct gss_api_mech *mech; enum rpc_gss_svc service; struct rpc_clnt *client; struct net *net;
netns_tracker ns_tracker; /* * There are two upcall pipes; dentry[1], named "gssd", is used * for the new text-based upcall; dentry[0] is named after the * mechanism (for example, "krb5") and exists for * backwards-compatibility with older gssd's.
*/ struct gss_pipe *gss_pipe[2]; constchar *target_name;
};
/* pipe_version >= 0 if and only if someone has a pipe open. */ static DEFINE_SPINLOCK(pipe_version_lock); staticstruct rpc_wait_queue pipe_version_rpc_waitqueue; static DECLARE_WAIT_QUEUE_HEAD(pipe_version_waitqueue); staticvoid gss_put_auth(struct gss_auth *gss_auth);
staticinlinevoid
gss_put_ctx(struct gss_cl_ctx *ctx)
{ if (refcount_dec_and_test(&ctx->count))
gss_free_ctx(ctx);
}
/* gss_cred_set_ctx: * called by gss_upcall_callback and gss_create_upcall in order * to set the gss context. The actual exchange of an old context * and a new one is protected by the pipe->lock.
*/ staticvoid
gss_cred_set_ctx(struct rpc_cred *cred, struct gss_cl_ctx *ctx)
{ struct gss_cred *gss_cred = container_of(cred, struct gss_cred, gc_base);
/* First unsigned int gives the remaining lifetime in seconds of the * credential - e.g. the remaining TGT lifetime for Kerberos or * the -t value passed to GSSD.
*/
p = simple_get_bytes(p, end, &timeout, sizeof(timeout)); if (IS_ERR(p)) goto err; if (timeout == 0)
timeout = GSSD_MIN_TIMEOUT;
ctx->gc_expiry = now + ((unsignedlong)timeout * HZ); /* Sequence number window. Determines the maximum number of * simultaneous requests
*/
p = simple_get_bytes(p, end, &window_size, sizeof(window_size)); if (IS_ERR(p)) goto err;
ctx->gc_win = window_size; /* gssd signals an error by passing ctx->gc_win = 0: */ if (ctx->gc_win == 0) { /* * in which case, p points to an error code. Anything other * than -EKEYEXPIRED gets converted to -EACCES.
*/
p = simple_get_bytes(p, end, &ret, sizeof(ret)); if (!IS_ERR(p))
p = (ret == -EKEYEXPIRED) ? ERR_PTR(-EKEYEXPIRED) :
ERR_PTR(-EACCES); goto err;
} /* copy the opaque wire context */
p = simple_get_netobj(p, end, &ctx->gc_wire_ctx); if (IS_ERR(p)) goto err; /* import the opaque security context */
p = simple_get_bytes(p, end, &seclen, sizeof(seclen)); if (IS_ERR(p)) goto err;
q = (constvoid *)((constchar *)p + seclen); if (unlikely(q > end || q < p)) {
p = ERR_PTR(-EFAULT); goto err;
}
ret = gss_import_sec_context(p, seclen, gm, &ctx->gc_gss_ctx, NULL, GFP_KERNEL); if (ret < 0) {
trace_rpcgss_import_ctx(ret);
p = ERR_PTR(ret); goto err;
}
/* is there any trailing data? */ if (q == end) {
p = q; goto done;
}
/* pull in acceptor name (if there is one) */
p = simple_get_netobj(q, end, &ctx->gc_acceptor); if (IS_ERR(p)) goto err;
done:
trace_rpcgss_context(window_size, ctx->gc_expiry, now, timeout,
ctx->gc_acceptor.len, ctx->gc_acceptor.data);
err: return p;
}
/* XXX: Need some documentation about why UPCALL_BUF_LEN is so small. * Is user space expecting no more than UPCALL_BUF_LEN bytes? * Note that there are now _two_ NI_MAXHOST sized data items * being passed in this string.
*/ #define UPCALL_BUF_LEN 256
/* Try to add an upcall to the pipefs queue. * If an upcall owned by our uid already exists, then we return a reference * to that upcall instead of adding the new upcall.
*/ staticinlinestruct gss_upcall_msg *
gss_add_msg(struct gss_upcall_msg *gss_msg)
{ struct rpc_pipe *pipe = gss_msg->pipe; struct gss_upcall_msg *old;
if (list_empty(&gss_msg->list)) return;
spin_lock(&pipe->lock); if (!list_empty(&gss_msg->list))
__gss_unhash_msg(gss_msg);
spin_unlock(&pipe->lock);
}
len = scnprintf(p, buflen, "mech=%s uid=%d", mech->gm_name,
from_kuid_munged(userns, gss_msg->uid));
buflen -= len;
p += len;
gss_msg->msg.len = len;
/* * target= is a full service principal that names the remote * identity that we are authenticating to.
*/ if (target_name) {
len = scnprintf(p, buflen, " target=%s", target_name);
buflen -= len;
p += len;
gss_msg->msg.len += len;
}
/* * gssd uses service= and srchost= to select a matching key from * the system's keytab to use as the source principal. * * service= is the service name part of the source principal, * or "*" (meaning choose any). * * srchost= is the hostname part of the source principal. When * not provided, gssd uses the local hostname.
*/ if (service_name) { char *c = strchr(service_name, '@');
if (!c)
len = scnprintf(p, buflen, " service=%s",
service_name); else
len = scnprintf(p, buflen, " service=%.*s srchost=%s",
(int)(c - service_name),
service_name, c + 1);
buflen -= len;
p += len;
gss_msg->msg.len += len;
}
if (mech->gm_upcall_enctypes) {
len = scnprintf(p, buflen, " enctypes=%s",
mech->gm_upcall_enctypes);
buflen -= len;
p += len;
gss_msg->msg.len += len;
}
trace_rpcgss_upcall_msg(gss_msg->databuf);
len = scnprintf(p, buflen, "\n"); if (len == 0) goto out_overflow;
gss_msg->msg.len += len;
gss_msg->msg.data = gss_msg->databuf; return 0;
out_overflow:
WARN_ON_ONCE(1); return -ENOMEM;
}
staticint gss_pipe_open(struct inode *inode, int new_version)
{ struct net *net = inode->i_sb->s_fs_info; struct sunrpc_net *sn = net_generic(net, sunrpc_net_id); int ret = 0;
spin_lock(&pipe_version_lock); if (sn->pipe_version < 0) { /* First open of any gss pipe determines the version: */
sn->pipe_version = new_version;
rpc_wake_up(&pipe_version_rpc_waitqueue);
wake_up(&pipe_version_waitqueue);
} elseif (sn->pipe_version != new_version) { /* Trying to open a pipe of a different version */
ret = -EBUSY; goto out;
}
atomic_inc(&sn->pipe_users);
out:
spin_unlock(&pipe_version_lock); return ret;
/* * NOTE: we have the opportunity to use different * parameters based on the input flavor (which must be a pseudoflavor)
*/ staticstruct gss_auth *
gss_create_new(conststruct rpc_auth_create_args *args, struct rpc_clnt *clnt)
{
rpc_authflavor_t flavor = args->pseudoflavor; struct gss_auth *gss_auth; struct gss_pipe *gss_pipe; struct rpc_auth * auth; int err = -ENOMEM; /* XXX? */
err = rpcauth_init_credcache(auth); if (err) goto err_put_mech; /* * Note: if we created the old pipe first, then someone who * examined the directory at the right moment might conclude * that we supported only the old pipe. So we instead create * the new pipe first.
*/
gss_pipe = gss_pipe_get(clnt, "gssd", &gss_upcall_ops_v1); if (IS_ERR(gss_pipe)) {
err = PTR_ERR(gss_pipe); goto err_destroy_credcache;
}
gss_auth->gss_pipe[1] = gss_pipe;
/* * Auths may be shared between rpc clients that were cloned from a * common client with the same xprt, if they also share the flavor and * target_name. * * The auth is looked up from the oldest parent sharing the same * cl_xprt, and the auth itself references only that common parent * (which is guaranteed to last as long as any of its descendants).
*/ staticstruct gss_auth *
gss_auth_find_or_add_hashed(conststruct rpc_auth_create_args *args, struct rpc_clnt *clnt, struct gss_auth *new)
{ struct gss_auth *gss_auth; unsignedlong hashval = (unsignedlong)clnt;
spin_lock(&gss_auth_hash_lock);
hash_for_each_possible(gss_auth_hash_table,
gss_auth,
hash,
hashval) { if (gss_auth->client != clnt) continue; if (gss_auth->rpc_auth.au_flavor != args->pseudoflavor) continue; if (gss_auth->target_name != args->target_name) { if (gss_auth->target_name == NULL) continue; if (args->target_name == NULL) continue; if (strcmp(gss_auth->target_name, args->target_name)) continue;
} if (!refcount_inc_not_zero(&gss_auth->rpc_auth.au_count)) continue; goto out;
} if (new)
hash_add(gss_auth_hash_table, &new->hash, hashval);
gss_auth = new;
out:
spin_unlock(&gss_auth_hash_lock); return gss_auth;
}
while (clnt != clnt->cl_parent) { struct rpc_clnt *parent = clnt->cl_parent; /* Find the original parent for this transport */ if (rcu_access_pointer(parent->cl_xpi.xpi_xpswitch) != xps) break;
clnt = parent;
}
/* Make a copy of the cred so that we can reference count it */ new = kzalloc(sizeof(*gss_cred), GFP_KERNEL); if (new) { struct auth_cred acred = {
.cred = gss_cred->gc_base.cr_cred,
}; struct gss_cl_ctx *ctx =
rcu_dereference_protected(gss_cred->gc_ctx, 1);
/* * gss_send_destroy_context will cause the RPCSEC_GSS to send a NULL RPC call * to the server with the GSS control procedure field set to * RPC_GSS_PROC_DESTROY. This should normally cause the server to release * all RPCSEC_GSS state associated with that context.
*/ staticvoid
gss_send_destroy_context(struct rpc_cred *cred)
{ struct gss_cred *gss_cred = container_of(cred, struct gss_cred, gc_base); struct gss_auth *gss_auth = container_of(cred->cr_auth, struct gss_auth, rpc_auth); struct gss_cl_ctx *ctx = rcu_dereference_protected(gss_cred->gc_ctx, 1); struct gss_cred *new; struct rpc_task *task;
new = gss_dup_cred(gss_auth, gss_cred); if (new) {
ctx->gc_proc = RPC_GSS_PROC_DESTROY;
trace_rpcgss_ctx_destroy(gss_cred);
task = rpc_call_null(gss_auth->client, &new->gc_base,
RPC_TASK_ASYNC); if (!IS_ERR(task))
rpc_put_task(task);
put_rpccred(&new->gc_base);
}
}
/* gss_destroy_cred (and gss_free_ctx) are used to clean up after failure * to create a new cred or context, so they check that things have been
* allocated before freeing them. */ staticvoid
gss_do_free_ctx(struct gss_cl_ctx *ctx)
{
gss_delete_sec_context(&ctx->gc_gss_ctx);
kfree(ctx->gc_wire_ctx.data);
kfree(ctx->gc_acceptor.data);
kfree(ctx);
}
if (!(cred = kzalloc(sizeof(*cred), gfp))) goto out_err;
rpcauth_init_cred(&cred->gc_base, acred, auth, &gss_credops); /* * Note: in order to force a call to call_refresh(), we deliberately * fail to flag the credential as RPCAUTH_CRED_UPTODATE.
*/
cred->gc_base.cr_flags = 1UL << RPCAUTH_CRED_NEW;
cred->gc_service = gss_auth->service;
cred->gc_principal = acred->principal;
kref_get(&gss_auth->kref); return &cred->gc_base;
/* did the ctx disappear or was it replaced by one with no acceptor? */ if (!ctx || !ctx->gc_acceptor.len) {
kfree(string);
string = NULL; goto out;
}
acceptor = &ctx->gc_acceptor;
/* * Did we find a new acceptor that's longer than the original? Allocate * a longer buffer and try again.
*/ if (len < acceptor->len) {
len = acceptor->len;
rcu_read_unlock();
kfree(string); goto realloc;
}
if (test_bit(RPCAUTH_CRED_NEW, &rc->cr_flags)) goto out; /* Don't match with creds that have expired. */
rcu_read_lock();
ctx = rcu_dereference(gss_cred->gc_ctx); if (!ctx || time_after(jiffies, ctx->gc_expiry)) {
rcu_read_unlock(); return 0;
}
rcu_read_unlock(); if (!test_bit(RPCAUTH_CRED_UPTODATE, &rc->cr_flags)) return 0;
out: if (acred->principal != NULL) { if (gss_cred->gc_principal == NULL) return 0;
ret = strcmp(acred->principal, gss_cred->gc_principal) == 0;
} else { if (gss_cred->gc_principal != NULL) return 0;
ret = uid_eq(rc->cr_cred->fsuid, acred->cred->fsuid);
} return ret;
}
/* * Marshal credentials. * * The expensive part is computing the verifier. We can't cache a * pre-computed version of the verifier because the seqno, which * is different every time, is included in the MIC.
*/ staticint gss_marshal(struct rpc_task *task, struct xdr_stream *xdr)
{ struct rpc_rqst *req = task->tk_rqstp; struct rpc_cred *cred = req->rq_cred; struct gss_cred *gss_cred = container_of(cred, struct gss_cred,
gc_base); struct gss_cl_ctx *ctx = gss_cred_get_ctx(cred);
__be32 *p, *cred_len;
u32 maj_stat = 0; struct xdr_netobj mic; struct kvec iov; struct xdr_buf verf_buf; int status;
u32 seqno;
/* Credential */
p = xdr_reserve_space(xdr, 7 * sizeof(*p) +
ctx->gc_wire_ctx.len); if (!p) goto marshal_failed;
*p++ = rpc_auth_gss;
cred_len = p++;
/* We compute the checksum for the verifier over the xdr-encoded bytes
* starting with the xid and ending at the end of the credential: */
iov.iov_base = req->rq_snd_buf.head[0].iov_base;
iov.iov_len = (u8 *)p - (u8 *)iov.iov_base;
xdr_buf_from_iov(&iov, &verf_buf);
p = xdr_reserve_space(xdr, sizeof(*p)); if (!p) goto marshal_failed;
*p++ = rpc_auth_gss;
mic.data = (u8 *)(p + 1);
maj_stat = gss_get_mic(ctx->gc_gss_ctx, &verf_buf, &mic); if (maj_stat == GSS_S_CONTEXT_EXPIRED) goto expired; elseif (maj_stat != 0) goto bad_mic; if (xdr_stream_encode_opaque_inline(xdr, (void **)&p, mic.len) < 0) goto marshal_failed;
status = 0;
out:
gss_put_ctx(ctx); return status;
expired:
clear_bit(RPCAUTH_CRED_UPTODATE, &cred->cr_flags);
status = -EKEYEXPIRED; goto out;
marshal_failed:
status = -EMSGSIZE; goto out;
bad_mic:
trace_rpcgss_get_mic(task, maj_stat);
status = -EIO; goto out;
}
staticint
gss_validate(struct rpc_task *task, struct xdr_stream *xdr)
{ struct rpc_cred *cred = task->tk_rqstp->rq_cred; struct gss_cl_ctx *ctx = gss_cred_get_ctx(cred);
__be32 *p, *seq = NULL;
u32 len, maj_stat; int status; int i = 1; /* don't recheck the first item */
p = xdr_inline_decode(xdr, 2 * sizeof(*p)); if (!p) goto validate_failed; if (*p++ != rpc_auth_gss) goto validate_failed;
len = be32_to_cpup(p); if (len > RPC_MAX_AUTH_SIZE) goto validate_failed;
p = xdr_inline_decode(xdr, len); if (!p) goto validate_failed;
seq = kmalloc(4, GFP_KERNEL); if (!seq) goto validate_failed;
maj_stat = gss_validate_seqno_mic(ctx, task->tk_rqstp->rq_seqnos[0], seq, p, len); /* RFC 2203 5.3.3.1 - compute the checksum of each sequence number in the cache */ while (unlikely(maj_stat == GSS_S_BAD_SIG && i < task->tk_rqstp->rq_seqno_count))
maj_stat = gss_validate_seqno_mic(ctx, task->tk_rqstp->rq_seqnos[i++], seq, p, len); if (maj_stat == GSS_S_CONTEXT_EXPIRED)
clear_bit(RPCAUTH_CRED_UPTODATE, &cred->cr_flags); if (maj_stat) goto bad_mic;
/* We leave it to unwrap to calculate au_rslack. For now we just
* calculate the length of the verifier: */ if (test_bit(RPCAUTH_AUTH_UPDATE_SLACK, &cred->cr_auth->au_flags))
cred->cr_auth->au_verfsize = XDR_QUADLEN(len) + 2;
status = 0;
out:
gss_put_ctx(ctx);
kfree(seq); return status;
validate_failed:
status = -EIO; goto out;
bad_mic:
trace_rpcgss_verify_mic(task, maj_stat);
status = -EACCES; goto out;
}
status = -EIO;
p = xdr_reserve_space(xdr, 2 * sizeof(*p)); if (!p) goto wrap_failed;
opaque_len = p++;
*p = cpu_to_be32(*rqstp->rq_seqnos);
if (rpcauth_wrap_req_encode(task, xdr)) goto wrap_failed;
status = alloc_enc_pages(rqstp); if (unlikely(status)) goto wrap_failed;
first = snd_buf->page_base >> PAGE_SHIFT;
inpages = snd_buf->pages + first;
snd_buf->pages = rqstp->rq_enc_pages;
snd_buf->page_base -= first << PAGE_SHIFT; /* * Move the tail into its own page, in case gss_wrap needs * more space in the head when wrapping. * * Still... Why can't gss_wrap just slide the tail down?
*/ if (snd_buf->page_len || snd_buf->tail[0].iov_len) { char *tmp;
tmp = page_address(rqstp->rq_enc_pages[rqstp->rq_enc_pages_num - 1]);
memcpy(tmp, snd_buf->tail[0].iov_base, snd_buf->tail[0].iov_len);
snd_buf->tail[0].iov_base = tmp;
}
offset = (u8 *)p - (u8 *)snd_buf->head[0].iov_base;
maj_stat = gss_wrap(ctx->gc_gss_ctx, offset, snd_buf, inpages); /* slack space should prevent this ever happening: */ if (unlikely(snd_buf->len > snd_buf->buflen)) {
status = -EIO; goto wrap_failed;
} /* We're assuming that when GSS_S_CONTEXT_EXPIRED, the encryption was
* done anyway, so it's safe to put the request on the wire: */ if (maj_stat == GSS_S_CONTEXT_EXPIRED)
clear_bit(RPCAUTH_CRED_UPTODATE, &cred->cr_flags); elseif (maj_stat) goto bad_wrap;
*opaque_len = cpu_to_be32(snd_buf->len - offset); /* guess whether the pad goes into the head or the tail: */ if (snd_buf->page_len || snd_buf->tail[0].iov_len)
iov = snd_buf->tail; else
iov = snd_buf->head;
p = iov->iov_base + iov->iov_len;
pad = xdr_pad_size(snd_buf->len - offset);
memset(p, 0, pad);
iov->iov_len += pad;
snd_buf->len += pad;
status = -EIO; if (ctx->gc_proc != RPC_GSS_PROC_DATA) { /* The spec seems a little ambiguous here, but I think that not * wrapping context destruction requests makes the most sense.
*/
status = rpcauth_wrap_req_encode(task, xdr); goto out;
} switch (gss_cred->gc_service) { case RPC_GSS_SVC_NONE:
status = rpcauth_wrap_req_encode(task, xdr); break; case RPC_GSS_SVC_INTEGRITY:
status = gss_wrap_req_integ(cred, ctx, task, xdr); break; case RPC_GSS_SVC_PRIVACY:
status = gss_wrap_req_priv(cred, ctx, task, xdr); break; default:
status = -EIO;
}
out:
gss_put_ctx(ctx); return status;
}
/** * gss_update_rslack - Possibly update RPC receive buffer size estimates * @task: rpc_task for incoming RPC Reply being unwrapped * @cred: controlling rpc_cred for @task * @before: XDR words needed before each RPC Reply message * @after: XDR words needed following each RPC Reply message *
*/ staticvoid gss_update_rslack(struct rpc_task *task, struct rpc_cred *cred, unsignedint before, unsignedint after)
{ struct rpc_auth *auth = cred->cr_auth;
/* opaque databody_integ<>; */ if (xdr_stream_decode_u32(xdr, &len)) goto unwrap_failed; if (len & 3) goto unwrap_failed;
offset = rcv_buf->len - xdr_stream_remaining(xdr); if (xdr_stream_decode_u32(xdr, &seqno)) goto unwrap_failed; if (seqno != *rqstp->rq_seqnos) goto bad_seqno; if (xdr_buf_subsegment(rcv_buf, &gss_data, offset, len)) goto unwrap_failed;
/* * The xdr_stream now points to the beginning of the * upper layer payload, to be passed below to * rpcauth_unwrap_resp_decode(). The checksum, which * follows the upper layer payload in @rcv_buf, is * located and parsed without updating the xdr_stream.
*/
/* opaque checksum<>; */
offset += len; if (xdr_decode_word(rcv_buf, offset, &len)) goto unwrap_failed;
offset += sizeof(__be32); if (offset + len > rcv_buf->len) goto unwrap_failed;
mic.len = len;
mic.data = kmalloc(len, GFP_KERNEL); if (ZERO_OR_NULL_PTR(mic.data)) goto unwrap_failed; if (read_bytes_from_xdr_buf(rcv_buf, offset, mic.data, mic.len)) goto unwrap_failed;
maj_stat = gss_verify_mic(ctx->gc_gss_ctx, &gss_data, &mic); if (maj_stat == GSS_S_CONTEXT_EXPIRED)
clear_bit(RPCAUTH_CRED_UPTODATE, &cred->cr_flags); if (maj_stat != GSS_S_COMPLETE) goto bad_mic;
p = xdr_inline_decode(xdr, 2 * sizeof(*p)); if (unlikely(!p)) goto unwrap_failed;
opaque_len = be32_to_cpup(p++);
offset = (u8 *)(p) - (u8 *)head->iov_base; if (offset + opaque_len > rcv_buf->len) goto unwrap_failed;
maj_stat = gss_unwrap(ctx->gc_gss_ctx, offset,
offset + opaque_len, rcv_buf); if (maj_stat == GSS_S_CONTEXT_EXPIRED)
clear_bit(RPCAUTH_CRED_UPTODATE, &cred->cr_flags); if (maj_stat != GSS_S_COMPLETE) goto bad_unwrap; /* gss_unwrap decrypted the sequence number */ if (be32_to_cpup(p++) != *rqstp->rq_seqnos) goto bad_seqno;
/* gss_unwrap redacts the opaque blob from the head iovec. * rcv_buf has changed, thus the stream needs to be reset.
*/
xdr_init_decode(xdr, rcv_buf, p, rqstp);
staticvoid __exit exit_rpcsec_gss(void)
{
unregister_pernet_subsys(&rpcsec_gss_net_ops);
gss_svc_shutdown();
rpcauth_unregister(&authgss_ops);
rcu_barrier(); /* Wait for completion of call_rcu()'s */
}
MODULE_ALIAS("rpc-auth-6");
MODULE_DESCRIPTION("Sun RPC Kerberos RPCSEC_GSS client authentication");
MODULE_LICENSE("GPL");
module_param_named(expired_cred_retry_delay,
gss_expired_cred_retry_delay,
uint, 0644);
MODULE_PARM_DESC(expired_cred_retry_delay, "Timeout (in seconds) until " "the RPC engine retries an expired credential");
module_param_named(key_expire_timeo,
gss_key_expire_timeo,
uint, 0644);
MODULE_PARM_DESC(key_expire_timeo, "Time (in seconds) at the end of a " "credential keys lifetime where the NFS layer cleans up " "prior to key expiration");
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.