/*
 * linux/net/sunrpc/svcauth.c
 *
 * The generic interface for RPC authentication on the server side.
 * 
 * Copyright (C) 1995, 1996 Olaf Kirch <okir@monad.swb.de>
 */

#include <linux/types.h>
#include <linux/sched.h>
#include <linux/sunrpc/types.h>
#include <linux/sunrpc/xdr.h>
#include <linux/sunrpc/svcauth.h>
#include <linux/sunrpc/svcsock.h>

#define RPCDBG_FACILITY	RPCDBG_AUTH

/*
 * Type of authenticator function
 */
typedef void	(*auth_fn_t)(struct svc_rqst *rqstp, u32 *statp, u32 *authp);

/*
 * Builtin auth flavors
 */
static void	svcauth_null(struct svc_rqst *rqstp, u32 *statp, u32 *authp);
static void	svcauth_unix(struct svc_rqst *rqstp, u32 *statp, u32 *authp);

/*
 * Max number of authentication flavors we support
 */
#define RPC_SVCAUTH_MAX	8

/*
 * Table of authenticators
 */
static auth_fn_t	authtab[RPC_SVCAUTH_MAX] = {
	svcauth_null,
	svcauth_unix,
	NULL,
};

void
svc_authenticate(struct svc_rqst *rqstp, u32 *statp, u32 *authp)
{
	u32		flavor;
	auth_fn_t	func;

	*statp = rpc_success;
	*authp = rpc_auth_ok;

	svc_getlong(&rqstp->rq_argbuf, flavor);
	flavor = ntohl(flavor);

	dprintk("svc: svc_authenticate (%d)\n", flavor);
	if (flavor >= RPC_SVCAUTH_MAX || !(func = authtab[flavor])) {
		*authp = rpc_autherr_badcred;
		return;
	}

	rqstp->rq_cred.cr_flavor = flavor;
	func(rqstp, statp, authp);
}

int
svc_auth_register(u32 flavor, auth_fn_t func)
{
	if (flavor >= RPC_SVCAUTH_MAX || authtab[flavor])
		return -EINVAL;
	authtab[flavor] = func;
	return 0;
}

void
svc_auth_unregister(u32 flavor)
{
	if (flavor < RPC_SVCAUTH_MAX)
		authtab[flavor] = NULL;
}

static void
svcauth_null(struct svc_rqst *rqstp, u32 *statp, u32 *authp)
{
	struct svc_buf	*argp = &rqstp->rq_argbuf;
	struct svc_buf	*resp = &rqstp->rq_resbuf;

	if ((argp->len -= 3) < 0) {
		*statp = rpc_garbage_args;
		return;
	}
	if (*(argp->buf)++ != 0) {	/* we already skipped the flavor */
		dprintk("svc: bad null cred\n");
		*authp = rpc_autherr_badcred;
		return;
	}
	if (*(argp->buf)++ != RPC_AUTH_NULL || *(argp->buf)++ != 0) {
		dprintk("svc: bad null verf\n");
		*authp = rpc_autherr_badverf;
		return;
	}

	/* Signal that mapping to nobody uid/gid is required */
	rqstp->rq_cred.cr_uid = (uid_t) -1;
	rqstp->rq_cred.cr_gid = (gid_t) -1;
	rqstp->rq_cred.cr_groups[0] = NOGROUP;

	/* Put NULL verifier */
	rqstp->rq_verfed = 1;
	svc_putlong(resp, RPC_AUTH_NULL);
	svc_putlong(resp, 0);
}

static void
svcauth_unix(struct svc_rqst *rqstp, u32 *statp, u32 *authp)
{
	struct svc_buf	*argp = &rqstp->rq_argbuf;
	struct svc_buf	*resp = &rqstp->rq_resbuf;
	struct svc_cred	*cred = &rqstp->rq_cred;
	u32		*bufp = argp->buf;
	int		len   = argp->len, slen, i;

	if ((len -= 3) < 0) {
		*statp = rpc_garbage_args;
		return;
	}

	bufp++;					/* length */
	bufp++;					/* time stamp */
	slen = (ntohl(*bufp++) + 3) >> 2;	/* machname length */
	if (slen > 64 || (len -= slen) < 0)
		goto badcred;
	bufp += slen;				/* skip machname */

	cred->cr_uid = ntohl(*bufp++);		/* uid */
	cred->cr_gid = ntohl(*bufp++);		/* gid */

	slen = ntohl(*bufp++);			/* gids length */
	if (slen > 16 || (len -= slen + 2) < 0)
		goto badcred;
	for (i = 0; i < NGROUPS && i < slen; i++)
		cred->cr_groups[i] = ntohl(*bufp++);
	if (i < NGROUPS)
		cred->cr_groups[i] = NOGROUP;
	bufp += (slen - i);

	if (*bufp++ != RPC_AUTH_NULL || *bufp++ != 0) {
		*authp = rpc_autherr_badverf;
		return;
	}

	argp->buf = bufp;
	argp->len = len;

	/* Put NULL verifier */
	rqstp->rq_verfed = 1;
	svc_putlong(resp, RPC_AUTH_NULL);
	svc_putlong(resp, 0);

	return;

badcred:
	*authp = rpc_autherr_badcred;
}
