
/*
 * UNFS3 mount protocol procedures
 * (C) 2004, Pascal Schmidt
 * see file LICENSE for license details
 */

#include "config.h"

#include <sys/types.h>
#include <sys/stat.h>
#include <rpc/rpc.h>
#include <limits.h>
#include <stdlib.h>
#include <string.h>
#ifndef WIN32
#include <syslog.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#endif				       /* WIN32 */
#include <fcntl.h>

#include "nfs.h"
#include "mount.h"
#include "daemon.h"
#include "fh.h"
#include "fh_cache.h"
#include "fd_cache.h"
#include "Config/exports.h"
#include "password.h"
#include "backend.h"

#ifndef PATH_MAX
# define PATH_MAX	4096
#endif

#define IS_SECURE(port) ((port) < 1024)

/*
 * number of active mounts
 *
 * only a guess since clients can crash and/or not sent UMNT calls
 */
static int mount_cnt = 0;

/* list of currently mounted directories */
static mountlist mount_list = NULL;

static char nonce[32] = "";

/*
 * add entry to mount list
 */
static void add_mount(const char *path, struct svc_req *rqstp)
{
    mountlist new;
    mountlist iter;
    struct in6_addr addr;
    char host[INET6_ADDRSTRLEN];

    new = malloc(sizeof(struct mountbody));
    if (!new) {
	logmsg(LOG_CRIT, "add_mount: Unable to allocate memory");
	return;
    }

    if (get_remote(rqstp, &addr)) {
	logmsg(LOG_CRIT, "add_mount: Cannot get client address");
	return;
    }

    inet_ntop(AF_INET6, &addr, host, sizeof(host));
    new->ml_hostname = malloc(strlen(host) + 1);
    if (!new->ml_hostname) {
	logmsg(LOG_CRIT, "add_mount: Unable to allocate memory");
	free(new);
	return;
    }

    new->ml_directory = malloc(strlen(path) + 1);
    if (!new->ml_directory) {
	logmsg(LOG_CRIT, "add_mount: Unable to allocate memory");
	free(new->ml_hostname);
	free(new);
	return;
    }

    /* initialize the new entry */
    new->ml_next = NULL;
    strcpy(new->ml_hostname, host);
    strcpy(new->ml_directory, path);

    iter = mount_list;
    if (iter) {
	while (iter->ml_next)
	    iter = iter->ml_next;
	iter->ml_next = new;
    } else
	mount_list = new;

    mount_cnt++;
}

/*
 * remove entries from mount list
 */
static void remove_mount(const char *path, struct svc_req *rqstp)
{
    mountlist iter, next, prev = NULL;
    struct in6_addr addr;
    char host[INET6_ADDRSTRLEN];

    if (get_remote(rqstp, &addr)) {
	logmsg(LOG_CRIT, "remove_mount: Cannot get client address");
	return;
    }

    inet_ntop(AF_INET6, &addr, host, sizeof(host));

    iter = mount_list;
    while (iter) {
	if (strcmp(iter->ml_hostname, host) == 0 &&
	    (!path || strcmp(iter->ml_directory, path) == 0)) {
	    if (prev)
		prev->ml_next = iter->ml_next;
	    else
		mount_list = iter->ml_next;

	    next = iter->ml_next;

	    free(iter->ml_hostname);
	    free(iter->ml_directory);
	    free(iter);

	    iter = next;

	    /* adjust mount count */
	    if (mount_cnt > 0)
		mount_cnt--;
	} else {
	    prev = iter;
	    iter = iter->ml_next;
	}
    }
}

void *mountproc_null_3_svc(U(void *argp), U(struct svc_req *rqstp))
{
    static void *result = NULL;

    return &result;
}

mountres3 *mountproc_mnt_3_svc(dirpath * argp, struct svc_req * rqstp)
{
    char buf[PATH_MAX];
    unfs3_fh_t fh;
    nfs_fh3 nfh;
    static char fhbuf[FH_MAXBUF];
    static mountres3 result;
    static int auth = AUTH_UNIX;
    int authenticated = 0;
    char *password;
    struct in6_addr addr;
    char host[INET6_ADDRSTRLEN];

    /* We need to modify the *argp pointer. Make a copy. */
    char *dpath = *argp;

    /* error out if not version 3 */
    if (rqstp->rq_vers != 3) {
	get_remote(rqstp, &addr);
	logmsg(LOG_INFO,
	       "%s attempted mount with unsupported protocol version",
	       inet_ntop(AF_INET6, &addr, host, sizeof(host)));
	result.fhs_status = MNT3ERR_INVAL;
	return &result;
    }

    /* Check for "mount commands" */
    if (strncmp(dpath, "@getnonce", sizeof("@getnonce") - 1) == 0) {
	if (backend_gen_nonce(nonce) < 0) {
	    result.fhs_status = MNT3ERR_IO;
	} else {
	    result.fhs_status = MNT3_OK;
	    result.mountres3_u.mountinfo.fhandle.fhandle3_len = 32;
	    result.mountres3_u.mountinfo.fhandle.fhandle3_val = nonce;
	    result.mountres3_u.mountinfo.auth_flavors.auth_flavors_len = 1;
	    result.mountres3_u.mountinfo.auth_flavors.auth_flavors_val =
		&auth;
	}
	return &result;
    } else if (strncmp(dpath, "@password:", sizeof("@password:") - 1) == 0) {
	char pw[PASSWORD_MAXLEN + 1];

	mnt_cmd_argument(&dpath, "@password:", pw, PASSWORD_MAXLEN);
	if (exports_options(dpath, rqstp, &password, NULL) != -1) {
	    authenticated = !strcmp(password, pw);
	}
	/* else leave authenticated unchanged */
    } else if (strncmp(dpath, "@otp:", sizeof("@otp:") - 1) == 0) {
	/* The otp from the client */
	char otp[PASSWORD_MAXLEN + 1];

	/* Our calculated otp */
	char hexdigest[32];

	mnt_cmd_argument(&dpath, "@otp:", otp, PASSWORD_MAXLEN);
	if (exports_options(dpath, rqstp, &password, NULL) != -1) {
	    otp_digest(nonce, password, hexdigest);

	    /* Compare our calculated digest with what the client submitted */
	    authenticated = !strncmp(hexdigest, otp, 32);

	    /* Change nonce */
	    backend_gen_nonce(nonce);
	}
	/* else leave authenticated unchanged */
    }

    if ((exports_opts & OPT_REMOVABLE) && export_point(dpath)) {
	/* Removable media export point. Do not call realpath; simply copy
	   path */
	strncpy(buf, dpath, PATH_MAX);
    } else if (!backend_realpath(dpath, buf)) {
	/* the given path does not exist */
	result.fhs_status = MNT3ERR_NOENT;
	return &result;
    }

    if (strlen(buf) + 1 > NFS_MAXPATHLEN) {
	get_remote(rqstp, &addr);
	logmsg(LOG_INFO, "%s attempted to mount jumbo path",
	       inet_ntop(AF_INET6, &addr, host, sizeof(host)));
	result.fhs_status = MNT3ERR_NAMETOOLONG;
	return &result;
    }

    if ((exports_options(buf, rqstp, &password, NULL) == -1)
	|| (!authenticated && password[0])
	|| (!(exports_opts & OPT_INSECURE) &&
	    !IS_SECURE(ntohs(get_port(rqstp))))
	) {
	/* not exported to this host or at all, or a password defined and not 
	   authenticated */
	result.fhs_status = MNT3ERR_ACCES;
	return &result;
    }

    fh = fh_comp(buf, rqstp, FH_DIR);

    if (!fh_valid(fh)) {
	get_remote(rqstp, &addr);
	logmsg(LOG_INFO, "%s attempted to mount non-directory",
	       inet_ntop(AF_INET6, &addr, host, sizeof(host)));
	result.fhs_status = MNT3ERR_NOTDIR;
	return &result;
    }

    add_mount(dpath, rqstp);

    nfh = fh_encode(&fh, fhbuf);

    result.fhs_status = MNT3_OK;
    result.mountres3_u.mountinfo.fhandle.fhandle3_len = nfh.data.data_len;
    result.mountres3_u.mountinfo.fhandle.fhandle3_val = nfh.data.data_val;
    result.mountres3_u.mountinfo.auth_flavors.auth_flavors_len = 1;
    result.mountres3_u.mountinfo.auth_flavors.auth_flavors_val = &auth;

    return &result;
}

mountlist *mountproc_dump_3_svc(U(void *argp), U(struct svc_req *rqstp))
{
    return &mount_list;
}

void *mountproc_umnt_3_svc(dirpath * argp, struct svc_req *rqstp)
{
    /* RPC times out if we use a NULL pointer */
    static void *result = NULL;

    remove_mount(*argp, rqstp);

    /* if no more mounts are active, flush all open file descriptors */
    if (mount_cnt == 0)
	fd_cache_purge();

    return &result;
}

void *mountproc_umntall_3_svc(U(void *argp), struct svc_req *rqstp)
{
    /* RPC times out if we use a NULL pointer */
    static void *result = NULL;

    remove_mount(NULL, rqstp);

    /* if no more mounts are active, flush all open file descriptors */
    if (mount_cnt == 0)
	fd_cache_purge();

    return &result;
}

exports *mountproc_export_3_svc(U(void *argp), U(struct svc_req *rqstp))
{
    return &exports_nfslist;
}
