HardenedBSD/libexec/rpc.rquotad/rquotad.c
Sean Eric Fagan aad5531e71 This exposes ZFS user and group quotas via the normal
quatactl(2) mechanism.  (Read-only at this point, however.)
In particular, this is to allow rpc.rquotad query quotas
for NFS mounts, allowing users to see their quotas on the
hosts using the datasets.

The changes specifically:

* Add new RPC entry points for querying quotas.
* Changes the library routines to allow non-UFS quotas.
* Changes rquotad to check for quotas on mounted filesystems,
rather than being limited to entries in /etc/fstab
* Lastly, adds a VFS entry-point for ZFS to query quotas.

Note that this makes one unavoidable behavioural change: if quotas
are enabled, then they can be queried, as opposed to the current
method of checking for quotas being specified in fstab.  (With
ZFS, if there are user or group quotas, they're used, always.)

Reviewed by:	delphij, mav
Approved by:	mav
Sponsored by:	iXsystems Inc
Differential Revision:	https://reviews.freebsd.org/D15886
2018-07-05 22:56:13 +00:00

318 lines
8.0 KiB
C

/*
* by Manuel Bouyer (bouyer@ensta.fr)
*
* There is no copyright, you can use it as you want.
*/
#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");
#include <sys/param.h>
#include <sys/mount.h>
#include <sys/file.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <ufs/ufs/quota.h>
#include <rpc/rpc.h>
#include <rpcsvc/rquota.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <ctype.h>
#include <errno.h>
#include <fstab.h>
#include <grp.h>
#include <libutil.h>
#include <pwd.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <err.h>
#include <string.h>
#include <syslog.h>
#include <unistd.h>
static void rquota_service_1(struct svc_req *request, SVCXPRT *transp);
static void rquota_service_2(struct svc_req *request, SVCXPRT *transp);
static void sendquota(struct svc_req *request, SVCXPRT *transp);
static void sendquota_extended(struct svc_req *request, SVCXPRT *transp);
static int getfsquota(int type, long id, char *path, struct dqblk *dqblk);
static int from_inetd = 1;
static int debug = 0;
static void
cleanup(int sig)
{
(void)sig;
(void)rpcb_unset(RQUOTAPROG, RQUOTAVERS, NULL);
exit(0);
}
int
main(int argc, char **argv)
{
SVCXPRT *transp;
int ok;
struct sockaddr_storage from;
socklen_t fromlen;
int vers;
int ch;
while ((ch = getopt(argc, argv, "d")) != -1) {
switch (ch) {
case 'd':
debug++;
break;
default:
break;
}
}
fromlen = sizeof(from);
if (getsockname(0, (struct sockaddr *)&from, &fromlen) < 0)
from_inetd = 0;
if (!from_inetd) {
if (!debug)
daemon(0, 0);
(void)rpcb_unset(RQUOTAPROG, RQUOTAVERS, NULL);
(void)signal(SIGINT, cleanup);
(void)signal(SIGTERM, cleanup);
(void)signal(SIGHUP, cleanup);
}
openlog("rpc.rquotad", LOG_CONS|LOG_PID, LOG_DAEMON);
/* create and register the service */
if (from_inetd) {
transp = svc_tli_create(0, NULL, NULL, 0, 0);
if (transp == NULL) {
syslog(LOG_ERR, "couldn't create udp service.");
exit(1);
}
vers = RQUOTAVERS;
ok = svc_reg(transp, RQUOTAPROG, RQUOTAVERS,
rquota_service_1, NULL);
if (ok) {
vers = EXT_RQUOTAVERS;
ok = svc_reg(transp, RQUOTAPROG, EXT_RQUOTAVERS,
rquota_service_2, NULL);
}
} else {
vers = RQUOTAVERS;
ok = svc_create(rquota_service_1,
RQUOTAPROG, RQUOTAVERS, "udp");
if (ok) {
vers = EXT_RQUOTAVERS;
ok = svc_create(rquota_service_2,
RQUOTAPROG, EXT_RQUOTAVERS, "udp");
}
}
if (!ok) {
syslog(LOG_ERR,
"unable to register (RQUOTAPROG, %s, %s)",
vers == RQUOTAVERS ? "RQUOTAVERS" : "EXT_RQUOTAVERS",
from_inetd ? "(inetd)" : "udp");
exit(1);
}
svc_run();
syslog(LOG_ERR, "svc_run returned");
exit(1);
}
static void
rquota_service_2(struct svc_req *request, SVCXPRT *transp)
{
switch (request->rq_proc) {
case NULLPROC:
(void)svc_sendreply(transp, (xdrproc_t)xdr_void, (char *)NULL);
break;
case RQUOTAPROC_GETQUOTA:
case RQUOTAPROC_GETACTIVEQUOTA:
sendquota_extended(request, transp);
break;
default:
svcerr_noproc(transp);
break;
}
if (from_inetd)
exit(0);
}
static void
rquota_service_1(struct svc_req *request, SVCXPRT *transp)
{
switch (request->rq_proc) {
case NULLPROC:
(void)svc_sendreply(transp, (xdrproc_t)xdr_void, (char *)NULL);
break;
case RQUOTAPROC_GETQUOTA:
case RQUOTAPROC_GETACTIVEQUOTA:
sendquota(request, transp);
break;
default:
svcerr_noproc(transp);
break;
}
if (from_inetd)
exit(0);
}
/* read quota for the specified id, and send it */
static void
sendquota(struct svc_req *request, SVCXPRT *transp)
{
struct getquota_args getq_args;
struct getquota_rslt getq_rslt;
struct dqblk dqblk;
struct timeval timev;
int scale;
bzero(&getq_args, sizeof(getq_args));
if (!svc_getargs(transp, (xdrproc_t)xdr_getquota_args, &getq_args)) {
svcerr_decode(transp);
return;
}
if (request->rq_cred.oa_flavor != AUTH_UNIX) {
/* bad auth */
getq_rslt.status = Q_EPERM;
} else if (!getfsquota(USRQUOTA, getq_args.gqa_uid, getq_args.gqa_pathp, &dqblk)) {
/* failed, return noquota */
getq_rslt.status = Q_NOQUOTA;
} else {
gettimeofday(&timev, NULL);
getq_rslt.status = Q_OK;
getq_rslt.getquota_rslt_u.gqr_rquota.rq_active = TRUE;
scale = 1 << flsll(dqblk.dqb_bhardlimit >> 32);
getq_rslt.getquota_rslt_u.gqr_rquota.rq_bsize =
DEV_BSIZE * scale;
getq_rslt.getquota_rslt_u.gqr_rquota.rq_bhardlimit =
dqblk.dqb_bhardlimit / scale;
getq_rslt.getquota_rslt_u.gqr_rquota.rq_bsoftlimit =
dqblk.dqb_bsoftlimit / scale;
getq_rslt.getquota_rslt_u.gqr_rquota.rq_curblocks =
dqblk.dqb_curblocks / scale;
getq_rslt.getquota_rslt_u.gqr_rquota.rq_fhardlimit =
dqblk.dqb_ihardlimit;
getq_rslt.getquota_rslt_u.gqr_rquota.rq_fsoftlimit =
dqblk.dqb_isoftlimit;
getq_rslt.getquota_rslt_u.gqr_rquota.rq_curfiles =
dqblk.dqb_curinodes;
getq_rslt.getquota_rslt_u.gqr_rquota.rq_btimeleft =
dqblk.dqb_btime - timev.tv_sec;
getq_rslt.getquota_rslt_u.gqr_rquota.rq_ftimeleft =
dqblk.dqb_itime - timev.tv_sec;
}
if (!svc_sendreply(transp, (xdrproc_t)xdr_getquota_rslt, &getq_rslt))
svcerr_systemerr(transp);
if (!svc_freeargs(transp, (xdrproc_t)xdr_getquota_args, &getq_args)) {
syslog(LOG_ERR, "unable to free arguments");
exit(1);
}
}
static void
sendquota_extended(struct svc_req *request, SVCXPRT *transp)
{
struct ext_getquota_args getq_args;
struct getquota_rslt getq_rslt;
struct dqblk dqblk;
struct timeval timev;
int scale;
bzero(&getq_args, sizeof(getq_args));
if (!svc_getargs(transp, (xdrproc_t)xdr_ext_getquota_args, &getq_args)) {
svcerr_decode(transp);
return;
}
if (request->rq_cred.oa_flavor != AUTH_UNIX) {
/* bad auth */
getq_rslt.status = Q_EPERM;
} else if (!getfsquota(getq_args.gqa_type, getq_args.gqa_id, getq_args.gqa_pathp, &dqblk)) {
/* failed, return noquota */
getq_rslt.status = Q_NOQUOTA;
} else {
gettimeofday(&timev, NULL);
getq_rslt.status = Q_OK;
getq_rslt.getquota_rslt_u.gqr_rquota.rq_active = TRUE;
scale = 1 << flsll(dqblk.dqb_bhardlimit >> 32);
getq_rslt.getquota_rslt_u.gqr_rquota.rq_bsize =
DEV_BSIZE * scale;
getq_rslt.getquota_rslt_u.gqr_rquota.rq_bhardlimit =
dqblk.dqb_bhardlimit / scale;
getq_rslt.getquota_rslt_u.gqr_rquota.rq_bsoftlimit =
dqblk.dqb_bsoftlimit / scale;
getq_rslt.getquota_rslt_u.gqr_rquota.rq_curblocks =
dqblk.dqb_curblocks / scale;
getq_rslt.getquota_rslt_u.gqr_rquota.rq_fhardlimit =
dqblk.dqb_ihardlimit;
getq_rslt.getquota_rslt_u.gqr_rquota.rq_fsoftlimit =
dqblk.dqb_isoftlimit;
getq_rslt.getquota_rslt_u.gqr_rquota.rq_curfiles =
dqblk.dqb_curinodes;
getq_rslt.getquota_rslt_u.gqr_rquota.rq_btimeleft =
dqblk.dqb_btime - timev.tv_sec;
getq_rslt.getquota_rslt_u.gqr_rquota.rq_ftimeleft =
dqblk.dqb_itime - timev.tv_sec;
}
if (!svc_sendreply(transp, (xdrproc_t)xdr_getquota_rslt, &getq_rslt))
svcerr_systemerr(transp);
if (!svc_freeargs(transp, (xdrproc_t)xdr_getquota_args, &getq_args)) {
syslog(LOG_ERR, "unable to free arguments");
exit(1);
}
}
/*
* gets the quotas for id, filesystem path.
* Return 0 if fail, 1 otherwise
*/
static int
getfsquota(int type, long id, char *path, struct dqblk *dqblk)
{
struct quotafile *qf;
/*
* Remote quota checking is limited to mounted filesystems.
* Since UFS and ZFS support the quota system calls, we
* only need to make an fstab object that has the path, and
* a blank name for the filesystem type.
* This allows the quota_open() call to work the way we
* expect it to.
*
* The static char declaration is because compiler warnings
* don't allow passing a const char * to a char *.
*/
int rv;
static char blank[] = "";
struct fstab fst;
fst.fs_file = path;
fst.fs_mntops = blank;
fst.fs_vfstype = blank;
if (type != USRQUOTA && type != GRPQUOTA)
return (0);
qf = quota_open(&fst, type, O_RDONLY);
if (debug)
warnx("quota_open(<%s, %s>, %d) returned %p",
fst.fs_file, fst.fs_mntops, type,
qf);
if (qf == NULL)
return (0);
rv = quota_read(qf, dqblk, id) == 0;
quota_close(qf);
if (debug)
warnx("getfsquota(%d, %ld, %s, %p) -> %d",
type, id, path, dqblk, rv);
return (rv);
}