/*
 * QUOTA    An implementation of the diskquota system for the LINUX
 *          operating system. QUOTA is implemented using the BSD systemcall
 *          interface as the means of communication with the user level.
 *          Should work for all filesystems because of integration into the
 *          VFS layer of the operating system.
 *          This is based on the Melbourne quota system wich uses both user and
 *          group quota files.
 *
 *          This part does the lookup of the info.
 *
 * Author:  Marco van Wieringen <mvw@planets.elm.net>
 *
 *          This program is free software; you can redistribute it and/or
 *          modify it under the terms of the GNU General Public License
 *          as published by the Free Software Foundation; either version
 *          2 of the License, or (at your option) any later version.
 */

#include "config.h"

#include <rpc/rpc.h>
#include <arpa/inet.h>
#include <paths.h>
#include <stdio.h>
#include <syslog.h>
#include <time.h>
#include <stdint.h>

#include "mntopt.h"
#include "quotaops.h"
#include "bylabel.h"
#include "rquota.h"
#include "quotaio.h"
#include "quotasys.h"
#include "dqblk_rpc.h"
#include "common.h"

#define STDIN_FILENO	0

#define TYPE_EXTENDED	0x01
#define ACTIVE		0x02

#define FACILITY	LOG_LOCAL7

#ifndef MAXPATHNAMELEN
#define MAXPATHNAMELEN BUFSIZ
#endif

#define NETTYPE AF_INET

/* Options from rquota_svc.c */
#define FL_AUTOFS 4
extern int flags;

extern char nfs_pseudoroot[PATH_MAX];

/*
 * Global unix authentication credentials.
 */
extern struct authunix_parms *unix_cred;

int in_group(gid_t * gids, uint32_t len, gid_t gid)
{
	gid_t *gidsp = gids + len;

	while (gidsp > gids)
		if (*(--gids) == gid)
			return 1;

	return 0;
}

static inline void servnet2utildqblk(struct util_dqblk *u, sq_dqblk * n)
{
	time_t now;

	time(&now);
	u->dqb_bhardlimit = n->rq_bhardlimit;
	u->dqb_bsoftlimit = n->rq_bsoftlimit;
	u->dqb_ihardlimit = n->rq_fhardlimit;
	u->dqb_isoftlimit = n->rq_fsoftlimit;
	u->dqb_curspace = ((qsize_t)n->rq_curblocks) << RPC_DQBLK_SIZE_BITS;
	u->dqb_curinodes = n->rq_curfiles;
	if (n->rq_btimeleft)
		u->dqb_btime = (int32_t)n->rq_btimeleft + now;
	else
		u->dqb_btime = 0;
	if (n->rq_ftimeleft)
		u->dqb_itime = (int32_t)n->rq_ftimeleft + now;
	else
		u->dqb_itime = 0;
}

/* XDR transports 32b variables exactly. Find smallest needed shift to fit
 * 64b variable into into 32 bits and to preserve precision as high as
 * possible. */
static int find_block_shift(qsize_t hard, qsize_t soft, qsize_t cur)
{
	int shift;
	qsize_t value = hard;

	if (value < soft)
		value = soft;
	if (value < cur)
		value = cur;
	value >>= 32;
	for (shift = QUOTABLOCK_BITS; value; shift++)
		value >>= 1;

	return shift;
}

static inline void servutil2netdqblk(struct rquota *n, struct util_dqblk *u)
{
	time_t now;
	int shift;

	shift = find_block_shift(u->dqb_bhardlimit, u->dqb_bsoftlimit,
		toqb(u->dqb_curspace));
	n->rq_bsize = 1 << shift;
	n->rq_bhardlimit = u->dqb_bhardlimit >> (shift - QUOTABLOCK_BITS);
	n->rq_bsoftlimit = u->dqb_bsoftlimit >> (shift - QUOTABLOCK_BITS);
	n->rq_fhardlimit = u->dqb_ihardlimit;
	n->rq_fsoftlimit = u->dqb_isoftlimit;
	n->rq_curblocks = toqb(u->dqb_curspace) >> (shift - QUOTABLOCK_BITS);
	n->rq_curfiles = u->dqb_curinodes;

	time(&now);
	if (u->dqb_btime)
		n->rq_btimeleft = difftime2net(u->dqb_btime, now);
	else
		n->rq_btimeleft = 0;
	if (u->dqb_itime)
		n->rq_ftimeleft = difftime2net(u->dqb_itime, now);
	else
		n->rq_ftimeleft = 0;
}

setquota_rslt *setquotainfo(int lflags, caddr_t * argp, struct svc_req *rqstp)
{
	static setquota_rslt result;

#if defined(RPC_SETQUOTA)
	union {
		setquota_args *args;
		ext_setquota_args *ext_args;
	} arguments;
	struct util_dqblk dqblk;
	struct dquot *dquot;
	struct mount_entry *mnt;
	char pathname[PATH_MAX] = {0};
	char *pathp = pathname;
	int id, qcmd, type;
	struct quota_handle *handles[2] = { NULL, NULL };

	/*
	 * First check authentication.
	 */
	if (lflags & TYPE_EXTENDED) {
		arguments.ext_args = (ext_setquota_args *) argp;

		id = arguments.ext_args->sqa_id;
		if (unix_cred->aup_uid != 0) {
			result.status = Q_EPERM;
			return (&result);
		}

		qcmd = arguments.ext_args->sqa_qcmd;
		type = arguments.ext_args->sqa_type;
		if (arguments.ext_args->sqa_pathp[0] != '/')
			sstrncpy(pathname, nfs_pseudoroot, PATH_MAX);
		sstrncat(pathname, arguments.ext_args->sqa_pathp, PATH_MAX);
		servnet2utildqblk(&dqblk, &arguments.ext_args->sqa_dqblk);
	}
	else {
		arguments.args = (setquota_args *) argp;

		id = arguments.args->sqa_id;
		if (unix_cred->aup_uid != 0) {
			result.status = Q_EPERM;
			return (&result);
		}

		qcmd = arguments.args->sqa_qcmd;
		type = USRQUOTA;
		if (arguments.args->sqa_pathp[0] != '/')
			sstrncpy(pathname, nfs_pseudoroot, PATH_MAX);
		sstrncat(pathname, arguments.args->sqa_pathp, PATH_MAX);
		servnet2utildqblk(&dqblk, &arguments.args->sqa_dqblk);
	}

	result.status = Q_NOQUOTA;
	result.setquota_rslt_u.sqr_rquota.rq_bsize = RPC_DQBLK_SIZE;

	if (init_mounts_scan(1, &pathp, MS_QUIET | MS_NO_MNTPOINT | MS_NFS_ALL | ((flags & FL_AUTOFS) ? 0 : MS_NO_AUTOFS)) < 0)
		goto out;
	if (!(mnt = get_next_mount())) {
		end_mounts_scan();
		goto out;
	}
	if (!(handles[0] = init_io(mnt, type, -1, 0))) {
		end_mounts_scan();
		goto out;
	}
	end_mounts_scan();
	if (!(dquot = handles[0]->qh_ops->read_dquot(handles[0], id)))
		goto out;
	if (qcmd == QCMD(Q_RPC_SETQLIM, type) || qcmd == QCMD(Q_RPC_SETQUOTA, type)) {
		dquot->dq_dqb.dqb_bsoftlimit = dqblk.dqb_bsoftlimit;
		dquot->dq_dqb.dqb_bhardlimit = dqblk.dqb_bhardlimit;
		dquot->dq_dqb.dqb_isoftlimit = dqblk.dqb_isoftlimit;
		dquot->dq_dqb.dqb_ihardlimit = dqblk.dqb_ihardlimit;
		dquot->dq_dqb.dqb_btime = dqblk.dqb_btime;
		dquot->dq_dqb.dqb_itime = dqblk.dqb_itime;
	}
	if (qcmd == QCMD(Q_RPC_SETUSE, type) || qcmd == QCMD(Q_RPC_SETQUOTA, type)) {
		dquot->dq_dqb.dqb_curspace = dqblk.dqb_curspace;
		dquot->dq_dqb.dqb_curinodes = dqblk.dqb_curinodes;
	}
	if (handles[0]->qh_ops->commit_dquot(dquot, COMMIT_LIMITS) == -1) {
		free(dquot);
		goto out;
	}
	free(dquot);
	result.status = Q_OK;
out:
	dispose_handle_list(handles);
#else
	result.status = Q_EPERM;
#endif
	return (&result);
}

getquota_rslt *getquotainfo(int lflags, caddr_t * argp, struct svc_req * rqstp)
{
	static getquota_rslt result;
	union {
		getquota_args *args;
		ext_getquota_args *ext_args;
	} arguments;
	struct dquot *dquot = NULL;
	struct mount_entry *mnt;
	char pathname[PATH_MAX] = {0};
	char *pathp = pathname;
	int id, type;
	struct quota_handle *handles[2] = { NULL, NULL };

	/*
	 * First check authentication.
	 */
	if (lflags & TYPE_EXTENDED) {
		arguments.ext_args = (ext_getquota_args *) argp;
		id = arguments.ext_args->gqa_id;
		type = arguments.ext_args->gqa_type;
		if (arguments.ext_args->gqa_pathp[0] != '/')
			sstrncpy(pathname, nfs_pseudoroot, PATH_MAX);
		sstrncat(pathname, arguments.ext_args->gqa_pathp, PATH_MAX);

		if (type == USRQUOTA && unix_cred->aup_uid && unix_cred->aup_uid != id) {
			result.status = Q_EPERM;
			return (&result);
		}

		if (type == GRPQUOTA && unix_cred->aup_uid && unix_cred->aup_gid != id &&
		    !in_group((gid_t *) unix_cred->aup_gids, unix_cred->aup_len, id)) {
			result.status = Q_EPERM;
			return (&result);
		}
	}
	else {
		arguments.args = (getquota_args *) argp;
		id = arguments.args->gqa_uid;
		type = USRQUOTA;
		if (arguments.ext_args->gqa_pathp[0] != '/')
			sstrncpy(pathname, nfs_pseudoroot, PATH_MAX);
		sstrncat(pathname, arguments.args->gqa_pathp, PATH_MAX);

		if (unix_cred->aup_uid && unix_cred->aup_uid != id) {
			result.status = Q_EPERM;
			return (&result);
		}
	}

	result.status = Q_NOQUOTA;

	if (init_mounts_scan(1, &pathp, MS_QUIET | MS_NO_MNTPOINT | MS_NFS_ALL | ((flags & FL_AUTOFS) ? 0 : MS_NO_AUTOFS)) < 0)
		goto out;
	if (!(mnt = get_next_mount())) {
		end_mounts_scan();
		goto out;
	}
	if (!(handles[0] = init_io(mnt, type, -1, IOI_READONLY))) {
		end_mounts_scan();
		goto out;
	}
	end_mounts_scan();
	if (!(lflags & ACTIVE) || QIO_ENABLED(handles[0]))
		dquot = handles[0]->qh_ops->read_dquot(handles[0], id);
	if (dquot) {
		result.status = Q_OK;
		result.getquota_rslt_u.gqr_rquota.rq_active =
			QIO_ENABLED(handles[0]) ? TRUE : FALSE;
		servutil2netdqblk(&result.getquota_rslt_u.gqr_rquota, &dquot->dq_dqb);
		free(dquot);
	}
out:
	dispose_handle_list(handles);
	return (&result);
}

/*
 * Map RPC-entrypoints to local function names.
 */
getquota_rslt *rquotaproc_getquota_1_svc(getquota_args * argp, struct svc_req * rqstp)
{
	return (getquotainfo(0, (caddr_t *) argp, rqstp));
}

getquota_rslt *rquotaproc_getactivequota_1_svc(getquota_args * argp, struct svc_req * rqstp)
{
	return (getquotainfo(ACTIVE, (caddr_t *) argp, rqstp));
}

getquota_rslt *rquotaproc_getquota_2_svc(ext_getquota_args * argp, struct svc_req * rqstp)
{
	return (getquotainfo(TYPE_EXTENDED, (caddr_t *) argp, rqstp));
}

getquota_rslt *rquotaproc_getactivequota_2_svc(ext_getquota_args * argp, struct svc_req * rqstp)
{
	return (getquotainfo(TYPE_EXTENDED | ACTIVE, (caddr_t *) argp, rqstp));
}

setquota_rslt *rquotaproc_setquota_1_svc(setquota_args * argp, struct svc_req * rqstp)
{
	return (setquotainfo(0, (caddr_t *) argp, rqstp));
}

setquota_rslt *rquotaproc_setactivequota_1_svc(setquota_args * argp, struct svc_req * rqstp)
{
	return (setquotainfo(ACTIVE, (caddr_t *) argp, rqstp));
}

setquota_rslt *rquotaproc_setquota_2_svc(ext_setquota_args * argp, struct svc_req * rqstp)
{
	return (setquotainfo(TYPE_EXTENDED, (caddr_t *) argp, rqstp));
}

setquota_rslt *rquotaproc_setactivequota_2_svc(ext_setquota_args * argp, struct svc_req * rqstp)
{
	return (setquotainfo(TYPE_EXTENDED | ACTIVE, (caddr_t *) argp, rqstp));
}