1/*	$OpenBSD: quota.c,v 1.39 2018/04/26 12:42:51 guenther Exp $	*/
2
3/*
4 * Copyright (c) 1980, 1990, 1993
5 *	The Regents of the University of California.  All rights reserved.
6 *
7 * This code is derived from software contributed to Berkeley by
8 * Robert Elz at The University of Melbourne.
9 *
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
12 * are met:
13 * 1. Redistributions of source code must retain the above copyright
14 *    notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 *    notice, this list of conditions and the following disclaimer in the
17 *    documentation and/or other materials provided with the distribution.
18 * 3. Neither the name of the University nor the names of its contributors
19 *    may be used to endorse or promote products derived from this software
20 *    without specific prior written permission.
21 *
22 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
23 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
25 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
26 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
27 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
28 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
29 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
31 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
32 * SUCH DAMAGE.
33 */
34
35/*
36 * Disk quota reporting program.
37 */
38#include <sys/param.h>	/* DEV_BSIZE dbtob */
39#include <sys/types.h>
40#include <sys/mount.h>
41#include <sys/socket.h>
42
43#include <ufs/ufs/quota.h>
44#include <ctype.h>
45#include <err.h>
46#include <errno.h>
47#include <fcntl.h>
48#include <fstab.h>
49#include <grp.h>
50#include <netdb.h>
51#include <pwd.h>
52#include <stdio.h>
53#include <stdlib.h>
54#include <string.h>
55#include <time.h>
56#include <unistd.h>
57
58#include <rpc/rpc.h>
59#include <rpc/pmap_prot.h>
60#include <rpcsvc/rquota.h>
61
62char *qfname = QUOTAFILENAME;
63char *qfextension[] = INITQFNAMES;
64
65struct quotause {
66	struct	quotause *next;
67	long	flags;
68	struct	dqblk dqblk;
69	char	fsname[PATH_MAX + 1];
70};
71#define	FOUND	0x01
72
73int	alldigits(char *);
74int	callaurpc(char *, int, int, int, xdrproc_t, void *, xdrproc_t, void *);
75int	getnfsquota(struct statfs *, struct fstab *, struct quotause *,
76	    long, int);
77struct quotause
78       *getprivs(long id, int quotatype);
79int	getufsquota(struct statfs *, struct fstab *, struct quotause *,
80	    long, int);
81void	heading(int, u_long, const char *, const char *);
82void	showgid(gid_t);
83void	showgrpname(const char *);
84void	showquotas(int, u_long, const char *);
85void	showuid(uid_t);
86void	showusrname(const char *);
87char   *timeprt(time_t seconds);
88int	ufshasquota(struct fstab *, int, char **);
89void	usage(void);
90
91int	qflag;
92int	vflag;
93
94int
95main(int argc, char *argv[])
96{
97	int ngroups;
98	gid_t mygid, gidset[NGROUPS_MAX];
99	int i, gflag = 0, uflag = 0;
100	int ch;
101	extern char *optarg;
102	extern int optind;
103
104	while ((ch = getopt(argc, argv, "ugvq")) != -1) {
105		switch(ch) {
106		case 'g':
107			gflag = 1;
108			break;
109		case 'u':
110			uflag = 1;
111			break;
112		case 'v':
113			vflag = 1;
114			break;
115		case 'q':
116			qflag = 1;
117			break;
118		default:
119			usage();
120		}
121	}
122	argc -= optind;
123	argv += optind;
124	if (!uflag && !gflag)
125		uflag = 1;
126	if (argc == 0) {
127		if (uflag)
128			showuid(getuid());
129		if (gflag) {
130			mygid = getgid();
131			ngroups = getgroups(NGROUPS_MAX, gidset);
132			if (ngroups < 0)
133				err(1, "getgroups");
134			showgid(mygid);
135			for (i = 0; i < ngroups; i++)
136				if (gidset[i] != mygid)
137					showgid(gidset[i]);
138		}
139		exit(0);
140	}
141	if (uflag && gflag)
142		usage();
143	if (uflag) {
144		for (; argc > 0; argc--, argv++) {
145			if (alldigits(*argv))
146				showuid(atoi(*argv));
147			else
148				showusrname(*argv);
149		}
150		exit(0);
151	}
152	if (gflag) {
153		for (; argc > 0; argc--, argv++) {
154			if (alldigits(*argv))
155				showgid(atoi(*argv));
156			else
157				showgrpname(*argv);
158		}
159		exit(0);
160	}
161	/* NOTREACHED */
162
163	exit(1);
164}
165
166void
167usage(void)
168{
169	fprintf(stderr, "%s\n%s\n%s\n",
170	    "usage: quota [-q | -v] [-gu]",
171	    "       quota [-q | -v] -g group ...",
172	    "       quota [-q | -v] -u user ...");
173	exit(1);
174}
175
176/*
177 * Print out quotas for a specified user identifier.
178 */
179void
180showuid(uid_t uid)
181{
182	struct passwd *pwd = getpwuid(uid);
183	uid_t myuid;
184	const char *name;
185
186	if (pwd == NULL)
187		name = "(no account)";
188	else
189		name = pwd->pw_name;
190	myuid = getuid();
191	if (uid != myuid && myuid != 0) {
192		warnx("%s (uid %u): permission denied", name, uid);
193		return;
194	}
195	showquotas(USRQUOTA, uid, name);
196}
197
198/*
199 * Print out quotas for a specified user name.
200 */
201void
202showusrname(const char *name)
203{
204	struct passwd *pwd = getpwnam(name);
205	uid_t myuid;
206
207	if (pwd == NULL) {
208		warnx("%s: unknown user", name);
209		return;
210	}
211	myuid = getuid();
212	if (pwd->pw_uid != myuid && myuid != 0) {
213		warnx("%s (uid %u): permission denied", pwd->pw_name,
214		    pwd->pw_uid);
215		return;
216	}
217	showquotas(USRQUOTA, pwd->pw_uid, pwd->pw_name);
218}
219
220/*
221 * Print out quotas for a specified group identifier.
222 */
223void
224showgid(gid_t gid)
225{
226	struct group *grp = getgrgid(gid);
227	int ngroups;
228	gid_t mygid, gidset[NGROUPS_MAX];
229	int i;
230	const char *name;
231
232	if (grp == NULL)
233		name = "(no entry)";
234	else
235		name = grp->gr_name;
236	mygid = getgid();
237	ngroups = getgroups(NGROUPS_MAX, gidset);
238	if (ngroups < 0) {
239		warn("getgroups");
240		return;
241	}
242	if (gid != mygid) {
243		for (i = 0; i < ngroups; i++)
244			if (gid == gidset[i])
245				break;
246		if (i >= ngroups && getuid() != 0) {
247			warnx("%s (gid %u): permission denied", name, gid);
248			return;
249		}
250	}
251	showquotas(GRPQUOTA, gid, name);
252}
253
254/*
255 * Print out quotas for a specified group name.
256 */
257void
258showgrpname(const char *name)
259{
260	struct group *grp = getgrnam(name);
261	int ngroups;
262	gid_t mygid, gidset[NGROUPS_MAX];
263	int i;
264
265	if (grp == NULL) {
266		warnx("%s: unknown group", name);
267		return;
268	}
269	mygid = getgid();
270	ngroups = getgroups(NGROUPS_MAX, gidset);
271	if (ngroups < 0) {
272		warn("getgroups");
273		return;
274	}
275	if (grp->gr_gid != mygid) {
276		for (i = 0; i < ngroups; i++)
277			if (grp->gr_gid == gidset[i])
278				break;
279		if (i >= ngroups && getuid() != 0) {
280			warnx("%s (gid %u): permission denied",
281			    grp->gr_name, grp->gr_gid);
282			return;
283		}
284	}
285	showquotas(GRPQUOTA, grp->gr_gid, grp->gr_name);
286}
287
288void
289showquotas(int type, u_long id, const char *name)
290{
291	struct quotause *qup;
292	struct quotause *quplist;
293	char *msgi, *msgb, *nam;
294	uid_t lines = 0;
295	static time_t now;
296
297	if (now == 0)
298		time(&now);
299	quplist = getprivs(id, type);
300	for (qup = quplist; qup; qup = qup->next) {
301		if (!vflag &&
302		    qup->dqblk.dqb_isoftlimit == 0 &&
303		    qup->dqblk.dqb_ihardlimit == 0 &&
304		    qup->dqblk.dqb_bsoftlimit == 0 &&
305		    qup->dqblk.dqb_bhardlimit == 0)
306			continue;
307		msgi = NULL;
308		if (qup->dqblk.dqb_ihardlimit &&
309		    qup->dqblk.dqb_curinodes >= qup->dqblk.dqb_ihardlimit)
310			msgi = "File limit reached on";
311		else if (qup->dqblk.dqb_isoftlimit &&
312		    qup->dqblk.dqb_curinodes >= qup->dqblk.dqb_isoftlimit) {
313			if (qup->dqblk.dqb_itime > now)
314				msgi = "In file grace period on";
315			else
316				msgi = "Over file quota on";
317		}
318		msgb = NULL;
319		if (qup->dqblk.dqb_bhardlimit &&
320		    qup->dqblk.dqb_curblocks >= qup->dqblk.dqb_bhardlimit)
321			msgb = "Block limit reached on";
322		else if (qup->dqblk.dqb_bsoftlimit &&
323		    qup->dqblk.dqb_curblocks >= qup->dqblk.dqb_bsoftlimit) {
324			if (qup->dqblk.dqb_btime > now)
325				msgb = "In block grace period on";
326			else
327				msgb = "Over block quota on";
328		}
329		if (qflag) {
330			if ((msgi != NULL || msgb != NULL) &&
331			    lines++ == 0)
332				heading(type, id, name, "");
333			if (msgi != NULL)
334				printf("\t%s %s\n", msgi, qup->fsname);
335			if (msgb != NULL)
336				printf("\t%s %s\n", msgb, qup->fsname);
337			continue;
338		}
339		if (vflag ||
340		    qup->dqblk.dqb_curblocks ||
341		    qup->dqblk.dqb_curinodes) {
342			if (lines++ == 0)
343				heading(type, id, name, "");
344			nam = qup->fsname;
345			if (strlen(qup->fsname) > 15) {
346				printf("%s\n", qup->fsname);
347				nam = "";
348			}
349			printf("%12s %7d%c %7d %7d %7s",
350			    nam,
351			    (int)(dbtob((u_quad_t)qup->dqblk.dqb_curblocks)
352				/ 1024),
353			    (msgb == NULL) ? ' ' : '*',
354			    (int)(dbtob((u_quad_t)qup->dqblk.dqb_bsoftlimit)
355				/ 1024),
356			    (int)(dbtob((u_quad_t)qup->dqblk.dqb_bhardlimit)
357				/ 1024),
358			    (msgb == NULL) ? ""
359			        : timeprt(qup->dqblk.dqb_btime));
360			printf(" %7d%c %7d %7d %7s\n",
361			    qup->dqblk.dqb_curinodes,
362			    (msgi == NULL) ? ' ' : '*',
363			    qup->dqblk.dqb_isoftlimit,
364			    qup->dqblk.dqb_ihardlimit,
365			    (msgi == NULL) ? ""
366			        : timeprt(qup->dqblk.dqb_itime)
367			);
368			continue;
369		}
370	}
371	if (!qflag && lines == 0)
372		heading(type, id, name, "none");
373}
374
375void
376heading(int type, u_long id, const char *name, const char *tag)
377{
378
379	printf("Disk quotas for %s %s (%cid %ld): %s\n", qfextension[type],
380	    name, *qfextension[type], id, tag);
381	if (!qflag && tag[0] == '\0') {
382		printf("%12s%8s%9s%8s%8s%9s%8s%8s%8s\n",
383		    "Filesystem",
384		    "KBytes",
385		    "quota",
386		    "limit",
387		    "grace",
388		    "files",
389		    "quota",
390		    "limit",
391		    "grace");
392	}
393}
394
395/*
396 * Calculate the grace period and return a printable string for it.
397 */
398char *
399timeprt(time_t seconds)
400{
401	time_t hours, minutes;
402	static char buf[20];
403	static time_t now;
404
405	if (now == 0)
406		time(&now);
407	if (now > seconds)
408		return ("none");
409	seconds -= now;
410	minutes = (seconds + 30) / 60;
411	hours = (minutes + 30) / 60;
412	if (hours >= 36) {
413		(void)snprintf(buf, sizeof buf, "%ddays",
414		    (int)((hours + 12) / 24));
415		return (buf);
416	}
417	if (minutes >= 60) {
418		(void)snprintf(buf, sizeof buf, "%2d:%d",
419		    (int)(minutes / 60), (int)(minutes % 60));
420		return (buf);
421	}
422	(void)snprintf(buf, sizeof buf, "%2d", (int)minutes);
423	return (buf);
424}
425
426/*
427 * Collect the requested quota information.
428 */
429struct quotause *
430getprivs(long id, int quotatype)
431{
432	struct quotause *qup, *quptail;
433	struct fstab *fs;
434	struct quotause *quphead;
435	struct statfs *fst;
436	int nfst, i;
437
438	qup = quphead = NULL;
439
440	nfst = getmntinfo(&fst, MNT_WAIT);
441	if (nfst == 0)
442		errx(2, "no filesystems mounted!");
443	setfsent();
444	for (i = 0; i < nfst; i++) {
445		if (qup == NULL) {
446			if ((qup = malloc(sizeof *qup)) == NULL)
447				errx(2, "out of memory");
448		}
449		if (strncmp(fst[i].f_fstypename, "nfs", MFSNAMELEN) == 0) {
450			if (getnfsquota(&fst[i], NULL, qup, id, quotatype) == 0)
451				continue;
452		} else if (!strncmp(fst[i].f_fstypename, "ffs", MFSNAMELEN) ||
453		    !strncmp(fst[i].f_fstypename, "ufs", MFSNAMELEN) ||
454		    !strncmp(fst[i].f_fstypename, "mfs", MFSNAMELEN)) {
455			/*
456			 * XXX
457			 * UFS filesystems must be in /etc/fstab, and must
458			 * indicate that they have quotas on (?!) This is quite
459			 * unlike SunOS where quotas can be enabled/disabled
460			 * on a filesystem independent of /etc/fstab, and it
461			 * will still print quotas for them.
462			 */
463			if ((fs = getfsspec(fst[i].f_mntfromspec)) == NULL)
464				continue;
465			if (getufsquota(&fst[i], fs, qup, id, quotatype) == 0)
466				continue;
467		} else
468			continue;
469		strncpy(qup->fsname, fst[i].f_mntonname, sizeof qup->fsname-1);
470		qup->fsname[sizeof qup->fsname-1] = '\0';
471		if (quphead == NULL)
472			quphead = qup;
473		else
474			quptail->next = qup;
475		quptail = qup;
476		quptail->next = 0;
477		qup = NULL;
478	}
479	free(qup);
480	endfsent();
481	return (quphead);
482}
483
484/*
485 * Check to see if a particular quota is to be enabled.
486 */
487int
488ufshasquota(struct fstab *fs, int type, char **qfnamep)
489{
490	static char initname, usrname[100], grpname[100];
491	static char buf[BUFSIZ];
492	char *opt, *cp;
493
494	cp = NULL;
495	if (!initname) {
496		(void)snprintf(usrname, sizeof usrname, "%s%s",
497		    qfextension[USRQUOTA], qfname);
498		(void)snprintf(grpname, sizeof grpname, "%s%s",
499		    qfextension[GRPQUOTA], qfname);
500		initname = 1;
501	}
502	strncpy(buf, fs->fs_mntops, sizeof buf);
503	buf[sizeof(buf) - 1] = '\0';
504	for (opt = strtok(buf, ","); opt; opt = strtok(NULL, ",")) {
505		if ((cp = strchr(opt, '=')))
506			*cp++ = '\0';
507		if (type == USRQUOTA && strcmp(opt, usrname) == 0)
508			break;
509		if (type == GRPQUOTA && strcmp(opt, grpname) == 0)
510			break;
511	}
512	if (!opt)
513		return (0);
514	if (cp) {
515		*qfnamep = cp;
516		return (1);
517	}
518	(void)snprintf(buf, sizeof buf, "%s/%s.%s",
519	    fs->fs_file, qfname, qfextension[type]);
520	*qfnamep = buf;
521	return (1);
522}
523
524int
525getufsquota(struct statfs *fst, struct fstab *fs, struct quotause *qup,
526    long id, int quotatype)
527{
528	char *qfpathname;
529	int fd, qcmd;
530
531	qcmd = QCMD(Q_GETQUOTA, quotatype);
532	if (!ufshasquota(fs, quotatype, &qfpathname))
533		return (0);
534
535	if (quotactl(fs->fs_file, qcmd, id, (char *)&qup->dqblk) != 0) {
536		if ((fd = open(qfpathname, O_RDONLY)) < 0) {
537			warn("%s", qfpathname);
538			return (0);
539		}
540		(void)lseek(fd, (off_t)(id * sizeof(struct dqblk)), SEEK_SET);
541		switch (read(fd, &qup->dqblk, sizeof(struct dqblk))) {
542		case 0:				/* EOF */
543			/*
544			 * Convert implicit 0 quota (EOF)
545			 * into an explicit one (zero'ed dqblk)
546			 */
547			memset((caddr_t)&qup->dqblk, 0, sizeof(struct dqblk));
548			break;
549		case sizeof(struct dqblk):	/* OK */
550			break;
551		default:		/* ERROR */
552			warn("%s", qfpathname);
553			close(fd);
554			return (0);
555		}
556		close(fd);
557	}
558	return (1);
559}
560
561int
562getnfsquota(struct statfs *fst, struct fstab *fs, struct quotause *qup,
563    long id, int quotatype)
564{
565	struct getquota_args gq_args;
566	struct getquota_rslt gq_rslt;
567	struct dqblk *dqp = &qup->dqblk;
568	struct timeval tv;
569	char *cp;
570
571	if (fst->f_flags & MNT_LOCAL)
572		return (0);
573
574	/*
575	 * rpc.rquotad does not support group quotas
576	 */
577	if (quotatype != USRQUOTA)
578		return (0);
579
580	/*
581	 * must be some form of "hostname:/path"
582	 */
583	cp = strchr(fst->f_mntfromname, ':');
584	if (cp == NULL) {
585		warnx("cannot find hostname for %s", fst->f_mntfromname);
586		return (0);
587	}
588
589	*cp = '\0';
590	if (cp[1] != '/') {
591		*cp = ':';
592		return (0);
593	}
594
595	gq_args.gqa_pathp = &cp[1];
596	gq_args.gqa_uid = id;
597	if (callaurpc(fst->f_mntfromname, RQUOTAPROG, RQUOTAVERS,
598	    RQUOTAPROC_GETQUOTA, xdr_getquota_args, &gq_args,
599	    xdr_getquota_rslt, &gq_rslt) != 0) {
600		*cp = ':';
601		return (0);
602	}
603
604	switch (gq_rslt.status) {
605	case Q_NOQUOTA:
606		break;
607	case Q_EPERM:
608		warnx("permission error, host: %s", fst->f_mntfromname);
609		break;
610	case Q_OK:
611		gettimeofday(&tv, NULL);
612			/* blocks*/
613		dqp->dqb_bhardlimit =
614		    gq_rslt.getquota_rslt_u.gqr_rquota.rq_bhardlimit *
615		    (gq_rslt.getquota_rslt_u.gqr_rquota.rq_bsize / DEV_BSIZE);
616		dqp->dqb_bsoftlimit =
617		    gq_rslt.getquota_rslt_u.gqr_rquota.rq_bsoftlimit *
618		    (gq_rslt.getquota_rslt_u.gqr_rquota.rq_bsize / DEV_BSIZE);
619		dqp->dqb_curblocks =
620		    gq_rslt.getquota_rslt_u.gqr_rquota.rq_curblocks *
621		    (gq_rslt.getquota_rslt_u.gqr_rquota.rq_bsize / DEV_BSIZE);
622			/* inodes */
623		dqp->dqb_ihardlimit =
624			gq_rslt.getquota_rslt_u.gqr_rquota.rq_fhardlimit;
625		dqp->dqb_isoftlimit =
626			gq_rslt.getquota_rslt_u.gqr_rquota.rq_fsoftlimit;
627		dqp->dqb_curinodes =
628			gq_rslt.getquota_rslt_u.gqr_rquota.rq_curfiles;
629			/* grace times */
630		dqp->dqb_btime =
631		    tv.tv_sec + gq_rslt.getquota_rslt_u.gqr_rquota.rq_btimeleft;
632		dqp->dqb_itime =
633		    tv.tv_sec + gq_rslt.getquota_rslt_u.gqr_rquota.rq_ftimeleft;
634		*cp = ':';
635		return (1);
636	default:
637		warnx("bad rpc result, host: %s", fst->f_mntfromname);
638		break;
639	}
640	*cp = ':';
641	return (0);
642}
643
644int
645callaurpc(char *host, int prognum, int versnum, int procnum,
646    xdrproc_t inproc, void *in, xdrproc_t outproc, void *out)
647{
648	struct sockaddr_in server_addr;
649	enum clnt_stat clnt_stat;
650	struct hostent *hp;
651	struct timeval timeout, tottimeout;
652
653	CLIENT *client = NULL;
654	int socket = RPC_ANYSOCK;
655
656	if ((hp = gethostbyname(host)) == NULL)
657		return ((int) RPC_UNKNOWNHOST);
658	timeout.tv_usec = 0;
659	timeout.tv_sec = 6;
660
661	memset(&server_addr, 0, sizeof server_addr);
662	memcpy(&server_addr.sin_addr, hp->h_addr, hp->h_length);
663	server_addr.sin_family = AF_INET;
664	server_addr.sin_port =  0;
665
666	if ((client = clntudp_create(&server_addr, prognum,
667	    versnum, timeout, &socket)) == NULL)
668		return ((int) rpc_createerr.cf_stat);
669
670	client->cl_auth = authunix_create_default();
671	tottimeout.tv_sec = 25;
672	tottimeout.tv_usec = 0;
673	clnt_stat = clnt_call(client, procnum, inproc, in,
674	    outproc, out, tottimeout);
675
676	return ((int) clnt_stat);
677}
678
679int
680alldigits(char *s)
681{
682	int c;
683
684	c = (unsigned char)*s++;
685	do {
686		if (!isdigit(c))
687			return (0);
688	} while ((c = (unsigned char)*s++));
689	return (1);
690}
691