/*
 * vi:set cin noet sw=4 tw=70:
 * Copyright 2006, International Business Machines Corporation and others.
 * All Rights Reserved.
 * 
 * This software has been released under the terms of the IBM Public
 * License.  For details, see the LICENSE file in the top-level source
 * directory or online at http://www.openafs.org/dl/license10.html
 */

/*
 * Filesystem export operations for Linux
 */
#include <afsconfig.h>
#include "afs/param.h"


#if !defined(AFS_NONFSTRANS) || defined(AFS_AIX_IAUTH_ENV)
#include <linux/module.h> /* early to avoid printf->printk mapping */
#include <linux/fs.h>
#include "afs/sysincludes.h"
#include "afsincludes.h"
#include "nfsclient.h"
#include <linux/sunrpc/svc.h>
#include <linux/sunrpc/svcauth.h>

static unsigned long authtab_addr = 0;
#if defined(module_param) && LINUX_VERSION_CODE > KERNEL_VERSION(2,6,9)
module_param(authtab_addr, long, 0);
#else
MODULE_PARM(authtab_addr, "l");
#endif
MODULE_PARM_DESC(authtab_addr, "Address of the authtab array.");

extern struct auth_ops *authtab[] __attribute__((weak));
static struct auth_ops **afs_authtab;
static struct auth_ops *afs_new_authtab[RPC_AUTH_MAXFLAVOR];
static struct auth_ops *afs_orig_authtab[RPC_AUTH_MAXFLAVOR];

static int whine_memory = 0;

afs_lock_t afs_xnfssrv;

struct nfs_server_thread {
    struct nfs_server_thread *next;	/* next in chain */
    pid_t pid;				/* pid of this thread */
    int active;				/* this thread is servicing an RPC */
    struct sockaddr_in client_addr;	/* latest client of this thread */
    int client_addrlen;
    afs_int32 uid;			/* AFS UID/PAG for this thread */
    afs_int32 code;			/* What should InitReq return? */
    int flavor;				/* auth flavor */
    uid_t client_uid;			/* UID claimed by client */
    gid_t client_gid;			/* GID claimed by client */
    gid_t client_g0, client_g1;		/* groups claimed by client */
};

static struct nfs_server_thread *nfssrv_list = 0;

static struct nfs_server_thread *find_nfs_thread(int create)
{
    struct nfs_server_thread *this;

    /* Check that this is an nfsd kernel thread */
    if (current->files != init_task.files || strcmp(current->comm, "nfsd"))
	return 0;

    ObtainWriteLock(&afs_xnfssrv, 804);
    for (this = nfssrv_list; this; this = this->next)
	if (this->pid == current->pid)
	    break;
    if (!this && create) {
	this = afs_osi_Alloc(sizeof(struct nfs_server_thread));
	if (this) {
	    this->next = nfssrv_list;
	    this->pid  = current->pid;
	    this->client_addrlen = 0;
	    nfssrv_list = this;
	    printk("afs: added nfsd task %d/%d\n",
		   current->tgid, current->pid);
	} else if (!whine_memory) {
	    whine_memory = 1;
	    printk("afs: failed to allocate memory for nfsd task %d/%d\n",
		   current->tgid, current->pid);
	}
    }
    ReleaseWriteLock(&afs_xnfssrv);
    return this;
}

static int
svcauth_afs_accept(struct svc_rqst *rqstp, u32 *authp)
{
    struct nfs_server_thread *ns;
    struct afs_exporter *outexp;
    afs_ucred_t *credp;
    struct sockaddr_in *addr;
    int code;

    code = afs_orig_authtab[rqstp->rq_authop->flavour]->accept(rqstp, authp);
    if (code != SVC_OK)
	return code;

    AFS_GLOCK();
    ns = find_nfs_thread(1);
    if (!ns) {
	AFS_GUNLOCK();
	/* XXX maybe we should fail this with rpc_system_err? */
	return SVC_OK;
    }
#if HAVE_LINUX_SVC_ADDR_IN
    addr = svc_addr_in(rqstp);
#else
    addr = &rqstp->rq_addr;
#endif

    ns->active		= 1;
    ns->flavor		= rqstp->rq_authop->flavour;
    ns->code		= EACCES;
    ns->client_addr	= *addr;
    ns->client_addrlen	= rqstp->rq_addrlen;
    ns->client_uid	= afs_cr_uid(&rqstp->rq_cred);
    ns->client_gid	= afs_cr_gid(&rqstp->rq_cred);
    if (afs_cr_group_info(&rqstp->rq_cred)->ngroups > 0)
	ns->client_g0	= GROUP_AT(afs_cr_group_info(&rqstp->rq_cred), 0);
    else
	ns->client_g0	= -1;
    if (afs_cr_group_info(&rqstp->rq_cred)->ngroups > 1)
	ns->client_g1	= GROUP_AT(afs_cr_group_info(&rqstp->rq_cred), 1);
    else
	ns->client_g1	= -1;

    if (addr->sin_family != AF_INET) {
	printk("afs: NFS request from non-IPv4 client (family %d len %d)\n",
	       addr->sin_family, rqstp->rq_addrlen);
	goto done;
    }

    credp = crget();
    afs_set_cr_uid(credp, afs_cr_uid(&rqstp->rq_cred));
    afs_set_cr_gid(credp, afs_cr_gid(&rqstp->rq_cred));
    get_group_info(afs_cr_group_info(&rqstp->rq_cred));
    afs_set_cr_group_info(credp, afs_cr_group_info(&rqstp->rq_cred));

    /* avoid creating wildcard entries by mapping anonymous
     * clients to afs_nobody */
    if (afs_cr_uid(credp) == -1)
	afs_set_cr_uid(credp, -2);
    code = afs_nfsclient_reqhandler(0, &credp, addr->sin_addr.s_addr,
				    &ns->uid, &outexp);
    if (!code && outexp) EXP_RELE(outexp);
    if (!code) ns->code = 0;
    if (code)
	printk("afs: svcauth_afs_accept: afs_nfsclient_reqhandler: %d\n", code);
    crfree(credp);

done:
    AFS_GUNLOCK();
    return SVC_OK;
}


int osi_linux_nfs_initreq(struct vrequest *av, afs_ucred_t *cr, int *code)
{
    struct nfs_server_thread *ns;

    ns = find_nfs_thread(0);
    if (!ns || !ns->active)
	return 0;

    *code = ns->code;
    if (!ns->code) {
	afs_cr_ruid(cr) = NFSXLATOR_CRED;
	av->uid = ns->uid;
    }
    return 1;
}

void osi_linux_nfssrv_init(void)
{
    int i;

    nfssrv_list = 0;
    AFS_RWLOCK_INIT(&afs_xnfssrv, "afs_xnfssrv");

    if (authtab && !IS_ERR(authtab))
	   afs_authtab = authtab;
    else if (authtab_addr) afs_authtab = (struct auth_ops **)authtab_addr;
    else {
	printk("Warning: Unable to find the address of authtab\n");
	printk("NFS Translator hooks will not be installed\n");
	printk("To correct, specify authtab_addr=<authtab>\n");
	afs_authtab = 0;
	return;
    }

    for (i = 0; i < RPC_AUTH_MAXFLAVOR; i++) {
	afs_orig_authtab[i] = afs_authtab[i];
	if (!afs_orig_authtab[i] || afs_orig_authtab[i]->flavour != i ||
	    !try_module_get(afs_orig_authtab[i]->owner)) {
	    afs_orig_authtab[i] = 0;
	    continue;
	}

	afs_new_authtab[i] = afs_osi_Alloc(sizeof(struct auth_ops));
	osi_Assert(afs_new_authtab[i] != NULL);
	*(afs_new_authtab[i]) = *(afs_orig_authtab[i]);
	afs_new_authtab[i]->owner = THIS_MODULE;
	afs_new_authtab[i]->accept = svcauth_afs_accept;
	/* afs_new_authtab[i]->release = svcauth_afs_release; */
	svc_auth_unregister(i);
	svc_auth_register(i, afs_new_authtab[i]);
    }
}

void osi_linux_nfssrv_shutdown(void)
{
    struct nfs_server_thread *next;
    int i;

    if (afs_authtab) {
	for (i = 0; i < RPC_AUTH_MAXFLAVOR; i++) {
	    if (!afs_orig_authtab[i])
		continue;
	    svc_auth_unregister(i);
	    svc_auth_register(i, afs_orig_authtab[i]);
	    module_put(afs_orig_authtab[i]->owner);
	    afs_osi_Free(afs_new_authtab[i], sizeof(struct auth_ops));
	}
    }

    AFS_GLOCK();
    ObtainWriteLock(&afs_xnfssrv, 805);
    while (nfssrv_list) {
	next = nfssrv_list->next;
	afs_osi_Free(nfssrv_list, sizeof(struct nfs_server_thread));
	nfssrv_list = next;
    }
    ReleaseWriteLock(&afs_xnfssrv);
    AFS_GUNLOCK();
}
#endif /* AFS_NONFSTRANS */

