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