#include <sys/cdefs.h>
#ifndef lint
__RCSID("$NetBSD: rquotad.c,v 1.23 2006/05/09 20:18:07 mrg Exp $");
#endif
#include <sys/param.h>
#include <sys/types.h>
#include <sys/mount.h>
#include <sys/file.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/event.h>
#include <sys/time.h>
#include <signal.h>
#include <sys/queue.h>
#include <sys/quota.h>
#include <stdio.h>
#include <fstab.h>
#include <ctype.h>
#include <stdlib.h>
#include <string.h>
#include <pwd.h>
#include <grp.h>
#include <errno.h>
#include <unistd.h>
#include <err.h>
#include <pthread.h>
#include <syslog.h>
#include <libutil.h>
#include <rpc/rpc.h>
#include <rpc/pmap_clnt.h>
#include "rquota.h"
int bindresvport_sa(int sd, struct sockaddr *sa);
void rquota_service(struct svc_req *request, SVCXPRT *transp);
void ext_rquota_service(struct svc_req *request, SVCXPRT *transp);
void sendquota(struct svc_req *request, int vers, SVCXPRT *transp);
int getfsquota(int type, int id, char *path, struct dqblk *dqblk);
int hasquota(struct statfs *fst, char **uqfnamep, char **gqfnamep);
void lock_fsq(void);
void unlock_fsq(void);
void check_mounts(void);
void sigmux(int);
void *rquotad_thread(void *arg);
#if 0
#define DEBUG(args...) printf(args)
#else
#define DEBUG(args...)
#endif
#define _PATH_NFS_CONF "/etc/nfs.conf"
#define _PATH_RQUOTAD_PID "/var/run/rquotad.pid"
struct nfs_conf_server {
int rquota_port;
int verbose;
};
const struct nfs_conf_server config_defaults =
{
0,
0
};
int config_read(struct nfs_conf_server *conf);
struct fsq_stat {
TAILQ_ENTRY(fsq_stat) chain;
char *mountdir;
char *uqfpathname;
char *gqfpathname;
fsid_t fsid;
dev_t st_dev;
};
TAILQ_HEAD(fsqhead,fsq_stat) fsqhead;
pthread_mutex_t fsq_mutex;
int gotterm = 0;
struct nfs_conf_server config;
const char *qfextension[] = INITQFNAMES;
void
sigmux(__unused int dummy)
{
gotterm = 1;
}
int
main(__unused int argc, __unused char *argv[])
{
SVCXPRT *transp;
struct sockaddr_in inetaddr;
int sockfd = 0, error;
pthread_attr_t pattr;
pthread_t thd;
int kq, rv;
struct kevent ke;
struct pidfh *pfh;
pid_t pid;
config = config_defaults;
config_read(&config);
openlog("rpc.rquotad", LOG_CONS|LOG_PID, LOG_DAEMON);
pfh = pidfile_open(_PATH_RQUOTAD_PID, 0644, &pid);
if (pfh == NULL) {
syslog(LOG_ERR, "can't open rquotad pidfile: %s (%d)", strerror(errno), errno);
if ((errno == EACCES) && getuid())
syslog(LOG_ERR, "rquotad is expected to be run as root, not as uid %d.", getuid());
else if (errno == EEXIST)
syslog(LOG_ERR, "rquotad already running, pid: %d", pid);
exit(2);
}
if (pidfile_write(pfh) == -1)
syslog(LOG_WARNING, "can't write to rquotad pidfile: %s (%d)", strerror(errno), errno);
pmap_unset(RQUOTAPROG, RQUOTAVERS);
pmap_unset(RQUOTAPROG, EXT_RQUOTAVERS);
signal(SIGINT, sigmux);
signal(SIGTERM, sigmux);
signal(SIGHUP, sigmux);
if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
syslog(LOG_ERR, "can't create UDP socket: %s (%d)", strerror(errno), errno);
exit(1);
}
inetaddr.sin_family = AF_INET;
inetaddr.sin_addr.s_addr = INADDR_ANY;
inetaddr.sin_port = htons(config.rquota_port);
inetaddr.sin_len = sizeof(inetaddr);
if (bindresvport_sa(sockfd, (struct sockaddr *) & inetaddr) < 0) {
syslog(LOG_ERR, "can't bind UDP addr: %s (%d)", strerror(errno), errno);
exit(1);
}
transp = svcudp_create(sockfd);
if (transp == NULL) {
syslog(LOG_ERR, "cannot create UDP service");
exit(1);
}
if (!svc_register(transp, RQUOTAPROG, RQUOTAVERS, rquota_service, IPPROTO_UDP)) {
syslog(LOG_ERR, "unable to register (RQUOTAPROG, RQUOTAVERS, UDP)");
exit(1);
}
if (!svc_register(transp, RQUOTAPROG, EXT_RQUOTAVERS, ext_rquota_service, IPPROTO_UDP)) {
syslog(LOG_ERR, "unable to register (RQUOTAPROG, EXT_RQUOTAVERS, UDP)");
exit(1);
}
error = pthread_mutex_init(&fsq_mutex, NULL);
if (error) {
syslog(LOG_ERR, "file system quota mutex init failed: %s (%d)", strerror(error), error);
exit(1);
}
TAILQ_INIT(&fsqhead);
check_mounts();
pthread_attr_init(&pattr);
pthread_attr_setdetachstate(&pattr, PTHREAD_CREATE_DETACHED);
error = pthread_create(&thd, &pattr, rquotad_thread, NULL);
if (error) {
syslog(LOG_ERR, "rquotad pthread_create: %s (%d)", strerror(error), error);
exit(1);
}
if ((kq = kqueue()) < 0) {
syslog(LOG_ERR, "kqueue: %s (%d)", strerror(errno), errno);
exit(1);
}
EV_SET(&ke, 0, EVFILT_FS, EV_ADD, 0, 0, 0);
rv = kevent(kq, &ke, 1, NULL, 0, NULL);
if (rv < 0) {
syslog(LOG_ERR, "kevent(EVFILT_FS): %s (%d)", strerror(errno), errno);
exit(1);
}
while (!gotterm) {
rv = kevent(kq, NULL, 0, &ke, 1, NULL);
if ((rv > 0) && !(ke.flags & EV_ERROR) && (ke.fflags & (VQ_MOUNT|VQ_UNMOUNT))) {
check_mounts();
}
}
alarm(1);
pmap_unset(RQUOTAPROG, RQUOTAVERS);
pmap_unset(RQUOTAPROG, EXT_RQUOTAVERS);
pidfile_remove(pfh);
exit(0);
}
void *
rquotad_thread(__unused void *arg)
{
sigset_t sigset;
sigemptyset(&sigset);
sigaddset(&sigset, SIGINT);
sigaddset(&sigset, SIGQUIT);
sigaddset(&sigset, SIGSYS);
sigaddset(&sigset, SIGPIPE);
sigaddset(&sigset, SIGTERM);
sigaddset(&sigset, SIGHUP);
sigaddset(&sigset, SIGUSR1);
sigaddset(&sigset, SIGUSR2);
sigaddset(&sigset, SIGABRT);
pthread_sigmask(SIG_BLOCK, &sigset, NULL);
svc_run();
syslog(LOG_ERR, "rquotad died");
exit(1);
}
void
rquota_service(struct svc_req *request, SVCXPRT *transp)
{
switch (request->rq_proc) {
case NULLPROC:
svc_sendreply(transp, (xdrproc_t)xdr_void, (char *)NULL);
break;
case RQUOTAPROC_GETQUOTA:
case RQUOTAPROC_GETACTIVEQUOTA:
sendquota(request, RQUOTAVERS, transp);
break;
default:
svcerr_noproc(transp);
break;
}
}
void
ext_rquota_service(struct svc_req *request, SVCXPRT *transp)
{
switch (request->rq_proc) {
case NULLPROC:
svc_sendreply(transp, (xdrproc_t)xdr_void, (char *)NULL);
break;
case RQUOTAPROC_GETQUOTA:
case RQUOTAPROC_GETACTIVEQUOTA:
sendquota(request, EXT_RQUOTAVERS, transp);
break;
default:
svcerr_noproc(transp);
break;
}
}
int
ismember(struct authunix_parms *aup, int gid)
{
uint g;
if (aup->aup_gid == gid)
return (1);
for (g=0; g < aup->aup_len; g++)
if (aup->aup_gids[g] == gid)
return (1);
return (0);
}
void
sendquota(struct svc_req *request, int vers, SVCXPRT *transp)
{
struct getquota_args getq_args;
struct ext_getquota_args ext_getq_args;
struct getquota_rslt getq_rslt;
struct dqblk dqblk;
struct timeval timev;
struct authunix_parms *aup;
memset((char *)&getq_args, 0, sizeof(getq_args));
memset((char *)&ext_getq_args, 0, sizeof(ext_getq_args));
switch (vers) {
case RQUOTAVERS:
if (!svc_getargs(transp, xdr_getquota_args,
(caddr_t)&getq_args)) {
svcerr_decode(transp);
return;
}
ext_getq_args.gqa_pathp = getq_args.gqa_pathp;
ext_getq_args.gqa_id = getq_args.gqa_uid;
ext_getq_args.gqa_type = RQUOTA_USRQUOTA;
break;
case EXT_RQUOTAVERS:
if (!svc_getargs(transp, xdr_ext_getquota_args,
(caddr_t)&ext_getq_args)) {
svcerr_decode(transp);
return;
}
break;
}
aup = (struct authunix_parms *)request->rq_clntcred;
if (request->rq_cred.oa_flavor != AUTH_UNIX) {
getq_rslt.status = Q_EPERM;
} else if ((ext_getq_args.gqa_type == RQUOTA_USRQUOTA) && aup->aup_uid &&
(aup->aup_uid != ext_getq_args.gqa_id)) {
getq_rslt.status = Q_EPERM;
} else if ((ext_getq_args.gqa_type == RQUOTA_GRPQUOTA) && aup->aup_uid &&
!ismember(aup, ext_getq_args.gqa_id)) {
getq_rslt.status = Q_EPERM;
} else if (!getfsquota(ext_getq_args.gqa_type, ext_getq_args.gqa_id,
ext_getq_args.gqa_pathp, &dqblk)) {
getq_rslt.status = Q_NOQUOTA;
} else {
gettimeofday(&timev, NULL);
getq_rslt.status = Q_OK;
getq_rslt.getquota_rslt_u.gqr_rquota.rq_active = TRUE;
getq_rslt.getquota_rslt_u.gqr_rquota.rq_bsize = DEV_BSIZE;
getq_rslt.getquota_rslt_u.gqr_rquota.rq_bhardlimit =
dqblk.dqb_bhardlimit / DEV_BSIZE;
getq_rslt.getquota_rslt_u.gqr_rquota.rq_bsoftlimit =
dqblk.dqb_bsoftlimit / DEV_BSIZE;
getq_rslt.getquota_rslt_u.gqr_rquota.rq_curblocks =
dqblk.dqb_curbytes / DEV_BSIZE;
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, (char *)&getq_rslt))
svcerr_systemerr(transp);
if (!svc_freeargs(transp, xdr_getquota_args, (caddr_t)&getq_args)) {
syslog(LOG_ERR, "unable to free arguments");
exit(1);
}
}
int
getfsquota(int type, int id, char *path, struct dqblk *dqblk)
{
struct stat st_path;
struct fsq_stat *fs;
int qcmd, fd, ret = 0;
char *filename;
qcmd = QCMD(Q_GETQUOTA, type == RQUOTA_USRQUOTA ? USRQUOTA : GRPQUOTA);
lock_fsq();
if (quotactl(path, qcmd, id, (char*)dqblk) == 0) {
ret = 1;
goto out;
}
if (stat(path, &st_path) < 0) {
ret = 0;
goto out;
}
TAILQ_FOREACH(fs, &fsqhead, chain) {
if (st_path.st_dev < fs->st_dev) {
ret = 0;
goto out;
}
if (fs->st_dev != st_path.st_dev)
continue;
filename = (type == RQUOTA_USRQUOTA) ?
fs->uqfpathname : fs->gqfpathname;
if (filename == NULL) {
ret = 0;
goto out;
}
if ((fd = open(filename, O_RDONLY)) < 0) {
syslog(LOG_WARNING, "open error: %s: %m", filename);
ret = 0;
goto out;
}
if (lseek(fd, (off_t)(id * sizeof(struct dqblk)), SEEK_SET)
== (off_t)-1) {
close(fd);
ret = 0;
goto out;
}
switch (read(fd, dqblk, sizeof(struct dqblk))) {
case 0:
memset((caddr_t) dqblk, 0, sizeof(struct dqblk));
ret = 1;
break;
case sizeof(struct dqblk):
ret = 1;
break;
default:
syslog(LOG_WARNING, "read error: %s: %m", filename);
close(fd);
ret = 0;
goto out;
}
close(fd);
}
out:
unlock_fsq();
return (ret);
}
int
hasquota(struct statfs *fst, char **uqfnamep, char **gqfnamep)
{
static char buf[MAXPATHLEN], ubuf[MAXPATHLEN], gbuf[MAXPATHLEN];
struct stat sb;
int qcnt = 0;
snprintf(buf, sizeof(buf), "%s/%s.%s", fst->f_mntonname, QUOTAOPSNAME, qfextension[USRQUOTA] );
if (stat(buf, &sb) == 0) {
snprintf(ubuf, sizeof(ubuf), "%s/%s.%s", fst->f_mntonname, QUOTAFILENAME, qfextension[USRQUOTA]);
*uqfnamep = ubuf;
qcnt++;
}
snprintf(buf, sizeof(buf), "%s/%s.%s", fst->f_mntonname, QUOTAOPSNAME, qfextension[GRPQUOTA] );
if (stat(buf, &sb) == 0) {
snprintf(gbuf, sizeof(gbuf), "%s/%s.%s", fst->f_mntonname, QUOTAFILENAME, qfextension[GRPQUOTA]);
*gqfnamep = gbuf;
qcnt++;
}
return (qcnt);
}
void
lock_fsq(void)
{
int error = pthread_mutex_lock(&fsq_mutex);
if (error)
syslog(LOG_ERR, "mutex lock failed: %s (%d)", strerror(error), error);
}
void
unlock_fsq(void)
{
int error = pthread_mutex_unlock(&fsq_mutex);
if (error)
syslog(LOG_ERR, "mutex unlock failed: %s (%d)", strerror(error), error);
}
static void
fsadd(struct statfs *fst)
{
struct fsq_stat *fs = NULL, *fs2;
char *uqfpathname = NULL, *gqfpathname = NULL;
struct stat st;
if (strcmp(fst->f_fstypename, "hfs") &&
strcmp(fst->f_fstypename, "ufs"))
return;
if (!hasquota(fst, &uqfpathname, &gqfpathname))
return;
fs = (struct fsq_stat *) malloc(sizeof(*fs));
if (fs == NULL) {
syslog(LOG_ERR, "can't malloc: %m");
return;
}
bzero(fs, sizeof(*fs));
fs->mountdir = strdup(fst->f_mntonname);
if (fs->mountdir == NULL) {
syslog(LOG_ERR, "can't strdup: %m");
goto failed;
}
if (uqfpathname) {
fs->uqfpathname = strdup(uqfpathname);
if (fs->uqfpathname == NULL) {
syslog(LOG_ERR, "can't strdup: %m");
goto failed;
}
}
if (gqfpathname) {
fs->gqfpathname = strdup(gqfpathname);
if (fs->gqfpathname == NULL) {
syslog(LOG_ERR, "can't strdup: %m");
goto failed;
}
}
if (stat(fst->f_mntonname, &st))
goto failed;
fs->st_dev = st.st_dev;
fs->fsid.val[0] = fst->f_fsid.val[0];
fs->fsid.val[1] = fst->f_fsid.val[1];
TAILQ_FOREACH(fs2, &fsqhead, chain) {
if (fs->st_dev < fs2->st_dev)
break;
}
if (fs2)
TAILQ_INSERT_BEFORE(fs2, fs, chain);
else
TAILQ_INSERT_TAIL(&fsqhead, fs, chain);
return;
failed:
if (fs->gqfpathname)
free(fs->gqfpathname);
if (fs->uqfpathname)
free(fs->uqfpathname);
if (fs->mountdir)
free(fs->mountdir);
free(fs);
return;
}
static void
fsdel(struct statfs *fst)
{
struct fsq_stat *fs;
TAILQ_FOREACH(fs, &fsqhead, chain) {
if ((fs->fsid.val[0] != fst->f_fsid.val[0]) ||
(fs->fsid.val[1] != fst->f_fsid.val[1]))
continue;
if (strcmp(fs->mountdir, fst->f_mntonname))
continue;
break;
}
if (!fs)
return;
TAILQ_REMOVE(&fsqhead, fs, chain);
if (fs->gqfpathname)
free(fs->gqfpathname);
if (fs->uqfpathname)
free(fs->uqfpathname);
if (fs->mountdir)
free(fs->mountdir);
free(fs);
}
static struct statfs *sfs[2];
static int size[2], cnt[2], cur, lastfscnt;
#define PREV ((cur + 1) & 1)
static int
sfscmp(const void *arg1, const void *arg2)
{
const struct statfs *sfs1 = arg1;
const struct statfs *sfs2 = arg2;
return strcmp(sfs1->f_mntonname, sfs2->f_mntonname);
}
static void
get_mounts(void)
{
cur = (cur + 1) % 2;
while (size[cur] < (lastfscnt = getfsstat(sfs[cur], size[cur] * sizeof(struct statfs), MNT_NOWAIT))) {
free(sfs[cur]);
size[cur] = lastfscnt + 32;
sfs[cur] = malloc(size[cur] * sizeof(struct statfs));
if (!sfs[cur])
err(1, "no memory");
}
cnt[cur] = lastfscnt;
qsort(sfs[cur], cnt[cur], sizeof(struct statfs), sfscmp);
}
void
check_mounts(void)
{
int i, j, cmp;
lock_fsq();
get_mounts();
for (i=j=0; (i < cnt[PREV]) && (j < cnt[cur]); ) {
cmp = sfscmp(&sfs[PREV][i], &sfs[cur][j]);
if (!cmp) {
i++;
j++;
continue;
}
if (cmp < 0) {
DEBUG("- %s\n", sfs[PREV][i].f_mntonname);
fsdel(&sfs[PREV][i]);
i++;
}
if (cmp > 0) {
DEBUG("+ %s\n", sfs[cur][j].f_mntonname);
fsadd(&sfs[cur][j]);
j++;
}
}
while (i < cnt[PREV]) {
DEBUG("- %s\n", sfs[PREV][i].f_mntonname);
fsdel(&sfs[PREV][i]);
i++;
}
while (j < cnt[cur]) {
DEBUG("+ %s\n", sfs[cur][j].f_mntonname);
fsadd(&sfs[cur][j]);
j++;
}
unlock_fsq();
}
int
config_read(struct nfs_conf_server *conf)
{
FILE *f;
size_t len, linenum = 0;
char *line, *p, *key, *value;
long val;
if (!(f = fopen(_PATH_NFS_CONF, "r"))) {
if (errno != ENOENT)
syslog(LOG_WARNING, "%s", _PATH_NFS_CONF);
return (1);
}
for (;(line = fparseln(f, &len, &linenum, NULL, 0)); free(line)) {
if (len <= 0)
continue;
p = line + len - 1;
while ((p > line) && isspace(*p))
*p-- = '\0';
key = line;
while (isspace(*key))
key++;
value = p = strchr(line, '=');
if (p)
do { *p-- = '\0'; } while ((p > line) && isspace(*p));
if (value)
do { value++; } while (isspace(*value));
if (strncmp(key, "nfs.server.", 11)) {
DEBUG("%4ld %s=%s\n", linenum, key, value ? value : "");
continue;
}
val = !value ? 1 : strtol(value, NULL, 0);
DEBUG("%4ld %s=%s (%d)\n", linenum, key, value ? value : "", val);
if (!strcmp(key, "nfs.server.rquota.port")) {
conf->rquota_port = val;
} else if (!strcmp(key, "nfs.server.verbose")) {
conf->verbose = val;
} else {
DEBUG("ignoring unknown config value: %4ld %s=%s\n", linenum, key, value ? value : "");
}
}
fclose(f);
return (0);
}