/* Unix SMB/CIFS implementation. support for quotas Copyright (C) Andrew Tridgell 1992-1998 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 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ /* * This is one of the most system dependent parts of Samba, and its * done a litle differently. Each system has its own way of doing * things :-( */ #include "includes.h" #include "smbd/smbd.h" #include "system/filesys.h" #undef DBGC_CLASS #define DBGC_CLASS DBGC_QUOTA #ifndef HAVE_SYS_QUOTAS /* just a quick hack because sysquotas.h is included before linux/quota.h */ #ifdef QUOTABLOCK_SIZE #undef QUOTABLOCK_SIZE #endif #ifdef WITH_QUOTAS #if defined(SUNOS5) /* Solaris */ #include #include #include #include #include /**************************************************************************** Allows querying of remote hosts for quotas on NFS mounted shares. Supports normal NFS and AMD mounts. Alan Romeril July 2K. ****************************************************************************/ #include #include #include #include #include static int my_xdr_getquota_rslt(XDR *xdrsp, struct getquota_rslt *gqr) { int quotastat; if (!xdr_int(xdrsp, "astat)) { DEBUG(6,("nfs_quotas: Status bad or zero\n")); return 0; } gqr->status = quotastat; if (!xdr_int(xdrsp, &gqr->getquota_rslt_u.gqr_rquota.rq_bsize)) { DEBUG(6,("nfs_quotas: Block size bad or zero\n")); return 0; } if (!xdr_bool(xdrsp, &gqr->getquota_rslt_u.gqr_rquota.rq_active)) { DEBUG(6,("nfs_quotas: Active bad or zero\n")); return 0; } if (!xdr_int(xdrsp, &gqr->getquota_rslt_u.gqr_rquota.rq_bhardlimit)) { DEBUG(6,("nfs_quotas: Hardlimit bad or zero\n")); return 0; } if (!xdr_int(xdrsp, &gqr->getquota_rslt_u.gqr_rquota.rq_bsoftlimit)) { DEBUG(6,("nfs_quotas: Softlimit bad or zero\n")); return 0; } if (!xdr_int(xdrsp, &gqr->getquota_rslt_u.gqr_rquota.rq_curblocks)) { DEBUG(6,("nfs_quotas: Currentblocks bad or zero\n")); return 0; } return (1); } static int my_xdr_getquota_args(XDR *xdrsp, struct getquota_args *args) { if (!xdr_string(xdrsp, &args->gqa_pathp, RQ_PATHLEN )) return(0); if (!xdr_int(xdrsp, &args->gqa_uid)) return(0); return (1); } /* Restricted to SUNOS5 for the moment, I haven`t access to others to test. */ static bool nfs_quotas(char *nfspath, uid_t euser_id, uint64_t *bsize, uint64_t *dfree, uint64_t *dsize) { uid_t uid = euser_id; struct dqblk D; char *mnttype = nfspath; CLIENT *clnt; struct getquota_rslt gqr; struct getquota_args args; char *cutstr, *pathname, *host, *testpath; int len; static struct timeval timeout = {2,0}; enum clnt_stat clnt_stat; bool ret = True; *bsize = *dfree = *dsize = (uint64_t)0; len=strcspn(mnttype, ":"); pathname=strstr(mnttype, ":"); cutstr = (char *) SMB_MALLOC(len+1); if (!cutstr) return False; memset(cutstr, '\0', len+1); host = strncat(cutstr,mnttype, sizeof(char) * len ); DEBUG(5,("nfs_quotas: looking for mount on \"%s\"\n", cutstr)); DEBUG(5,("nfs_quotas: of path \"%s\"\n", mnttype)); testpath=strchr_m(mnttype, ':'); args.gqa_pathp = testpath+1; args.gqa_uid = uid; DEBUG(5,("nfs_quotas: Asking for host \"%s\" rpcprog \"%i\" rpcvers \"%i\" network \"%s\"\n", host, RQUOTAPROG, RQUOTAVERS, "udp")); if ((clnt = clnt_create(host, RQUOTAPROG, RQUOTAVERS, "udp")) == NULL) { ret = False; goto out; } clnt->cl_auth = authunix_create_default(); DEBUG(9,("nfs_quotas: auth_success\n")); clnt_stat=clnt_call(clnt, RQUOTAPROC_GETQUOTA, my_xdr_getquota_args, (caddr_t)&args, my_xdr_getquota_rslt, (caddr_t)&gqr, timeout); if (clnt_stat != RPC_SUCCESS) { DEBUG(9,("nfs_quotas: clnt_call fail\n")); ret = False; goto out; } /* * gqr.status returns 1 if quotas exist, 2 if there is * no quota set, and 3 if no permission to get the quota. * If 3, return something sensible. */ switch (gqr.status) { case 1: DEBUG(9,("nfs_quotas: Good quota data\n")); D.dqb_bsoftlimit = gqr.getquota_rslt_u.gqr_rquota.rq_bsoftlimit; D.dqb_bhardlimit = gqr.getquota_rslt_u.gqr_rquota.rq_bhardlimit; D.dqb_curblocks = gqr.getquota_rslt_u.gqr_rquota.rq_curblocks; break; case 2: case 3: D.dqb_bsoftlimit = 1; D.dqb_curblocks = 1; DEBUG(9,("nfs_quotas: Remote Quotas returned \"%i\" \n", gqr.status)); break; default: DEBUG(9, ("nfs_quotas: Unknown Remote Quota Status \"%i\"\n", gqr.status)); ret = false; goto out; } DEBUG(10,("nfs_quotas: Let`s look at D a bit closer... status \"%i\" bsize \"%i\" active? \"%i\" bhard \"%i\" bsoft \"%i\" curb \"%i\" \n", gqr.status, gqr.getquota_rslt_u.gqr_rquota.rq_bsize, gqr.getquota_rslt_u.gqr_rquota.rq_active, gqr.getquota_rslt_u.gqr_rquota.rq_bhardlimit, gqr.getquota_rslt_u.gqr_rquota.rq_bsoftlimit, gqr.getquota_rslt_u.gqr_rquota.rq_curblocks)); *bsize = gqr.getquota_rslt_u.gqr_rquota.rq_bsize; *dsize = D.dqb_bsoftlimit; if (D.dqb_curblocks > D.dqb_bsoftlimit) { *dfree = 0; *dsize = D.dqb_curblocks; } else *dfree = D.dqb_bsoftlimit - D.dqb_curblocks; out: if (clnt) { if (clnt->cl_auth) auth_destroy(clnt->cl_auth); clnt_destroy(clnt); } DEBUG(5,("nfs_quotas: For path \"%s\" returning bsize %.0f, dfree %.0f, dsize %.0f\n",args.gqa_pathp,(double)*bsize,(double)*dfree,(double)*dsize)); SAFE_FREE(cutstr); DEBUG(10,("nfs_quotas: End of nfs_quotas\n" )); return ret; } /**************************************************************************** try to get the disk space from disk quotas (SunOS & Solaris2 version) Quota code by Peter Urbanec (amiga@cse.unsw.edu.au). ****************************************************************************/ bool disk_quotas(connection_struct *conn, const char *path, uint64_t *bsize, uint64_t *dfree, uint64_t *dsize) { uid_t euser_id; int ret; struct dqblk D; struct quotctl command; int file; struct mnttab mnt; char *name = NULL; FILE *fd; SMB_STRUCT_STAT sbuf; SMB_DEV_T devno; bool found = false; euser_id = geteuid(); if (sys_stat(path, &sbuf, false) == -1) { return false; } devno = sbuf.st_ex_dev ; DEBUG(5,("disk_quotas: looking for path \"%s\" devno=%x\n", path, (unsigned int)devno)); if ((fd = fopen(MNTTAB, "r")) == NULL) { return false; } while (getmntent(fd, &mnt) == 0) { if (sys_stat(mnt.mnt_mountp, &sbuf, false) == -1) { continue; } DEBUG(5,("disk_quotas: testing \"%s\" devno=%x\n", mnt.mnt_mountp, (unsigned int)devno)); /* quotas are only on vxfs, UFS or NFS */ if ((sbuf.st_ex_dev == devno) && ( strcmp( mnt.mnt_fstype, MNTTYPE_UFS ) == 0 || strcmp( mnt.mnt_fstype, "nfs" ) == 0 || strcmp( mnt.mnt_fstype, "vxfs" ) == 0 )) { found = true; name = talloc_asprintf(talloc_tos(), "%s/quotas", mnt.mnt_mountp); break; } } fclose(fd); if (!found) { return false; } if (!name) { return false; } become_root(); if (strcmp(mnt.mnt_fstype, "nfs") == 0) { bool retval; DEBUG(5,("disk_quotas: looking for mountpath (NFS) \"%s\"\n", mnt.mnt_special)); retval = nfs_quotas(mnt.mnt_special, euser_id, bsize, dfree, dsize); unbecome_root(); return retval; } DEBUG(5,("disk_quotas: looking for quotas file \"%s\"\n", name)); if((file=open(name, O_RDONLY,0))<0) { unbecome_root(); return false; } command.op = Q_GETQUOTA; command.uid = euser_id; command.addr = (caddr_t) &D; ret = ioctl(file, Q_QUOTACTL, &command); close(file); unbecome_root(); if (ret < 0) { DEBUG(5,("disk_quotas ioctl (Solaris) failed. Error = %s\n", strerror(errno) )); return false; } /* If softlimit is zero, set it equal to hardlimit. */ if (D.dqb_bsoftlimit==0) { D.dqb_bsoftlimit = D.dqb_bhardlimit; } /* Use softlimit to determine disk space. A user exceeding the quota * is told that there's no space left. Writes might actually work for * a bit if the hardlimit is set higher than softlimit. Effectively * the disk becomes made of rubber latex and begins to expand to * accommodate the user :-) */ if (D.dqb_bsoftlimit==0) return(False); *bsize = DEV_BSIZE; *dsize = D.dqb_bsoftlimit; if (D.dqb_curblocks > D.dqb_bsoftlimit) { *dfree = 0; *dsize = D.dqb_curblocks; } else { *dfree = D.dqb_bsoftlimit - D.dqb_curblocks; } DEBUG(5,("disk_quotas for path \"%s\" returning " "bsize %.0f, dfree %.0f, dsize %.0f\n", path,(double)*bsize,(double)*dfree,(double)*dsize)); return true; } #else /* not Solaris */ #if AIX /* AIX quota patch from Ole Holm Nielsen */ #include /* AIX 4.X: Rename members of the dqblk structure (ohnielse@fysik.dtu.dk) */ #define dqb_curfiles dqb_curinodes #define dqb_fhardlimit dqb_ihardlimit #define dqb_fsoftlimit dqb_isoftlimit #ifdef _AIXVERSION_530 #include #include #endif /* AIX 5.3 */ #else /* !AIX */ #include #include #endif /**************************************************************************** try to get the disk space from disk quotas - default version ****************************************************************************/ bool disk_quotas(connection_struct *conn, const char *path, uint64_t *bsize, uint64_t *dfree, uint64_t *dsize) { int r; struct dqblk D; uid_t euser_id; #if !defined(AIX) char dev_disk[256]; SMB_STRUCT_STAT S; /* find the block device file */ if ((sys_stat(path, &S, false)<0) || (devnm(S_IFBLK, S.st_ex_dev, dev_disk, 256, 0)<0)) return (False); #endif /* !defined(AIX) */ euser_id = geteuid(); #if defined(AIX) /* AIX has both USER and GROUP quotas: Get the USER quota (ohnielse@fysik.dtu.dk) */ #ifdef _AIXVERSION_530 { struct statfs statbuf; quota64_t user_quota; if (statfs(path,&statbuf) != 0) return False; if(statbuf.f_vfstype == MNT_J2) { /* For some reason we need to be root for jfs2 */ become_root(); r = quotactl(path,QCMD(Q_J2GETQUOTA,USRQUOTA),euser_id,(char *) &user_quota); unbecome_root(); /* Copy results to old struct to let the following code work as before */ D.dqb_curblocks = user_quota.bused; D.dqb_bsoftlimit = user_quota.bsoft; D.dqb_bhardlimit = user_quota.bhard; D.dqb_curfiles = user_quota.iused; D.dqb_fsoftlimit = user_quota.isoft; D.dqb_fhardlimit = user_quota.ihard; } else if(statbuf.f_vfstype == MNT_JFS) { #endif /* AIX 5.3 */ save_re_uid(); if (set_re_uid() != 0) return False; r= quotactl(path,QCMD(Q_GETQUOTA,USRQUOTA),euser_id,(char *) &D); restore_re_uid(); #ifdef _AIXVERSION_530 } else r = 1; /* Fail for other FS-types */ } #endif /* AIX 5.3 */ #else /* !AIX */ r=quotactl(Q_GETQUOTA, dev_disk, euser_id, &D); #endif /* !AIX */ /* Use softlimit to determine disk space, except when it has been exceeded */ *bsize = 1024; if (r) { if (errno == EDQUOT) { *dfree =0; *dsize =D.dqb_curblocks; return (True); } else return(False); } /* If softlimit is zero, set it equal to hardlimit. */ if (D.dqb_bsoftlimit==0) D.dqb_bsoftlimit = D.dqb_bhardlimit; if (D.dqb_bsoftlimit==0) return(False); /* Use softlimit to determine disk space, except when it has been exceeded */ if ((D.dqb_curblocks>D.dqb_bsoftlimit) ||((D.dqb_curfiles>D.dqb_fsoftlimit) && (D.dqb_fsoftlimit != 0)) ) { *dfree = 0; *dsize = D.dqb_curblocks; } else { *dfree = D.dqb_bsoftlimit - D.dqb_curblocks; *dsize = D.dqb_bsoftlimit; } return (True); } #endif /* Solaris */ #else /* WITH_QUOTAS */ bool disk_quotas(connection_struct *conn, const char *path, uint64_t *bsize, uint64_t *dfree, uint64_t *dsize) { (*bsize) = 512; /* This value should be ignored */ /* And just to be sure we set some values that hopefully */ /* will be larger that any possible real-world value */ (*dfree) = (uint64_t)-1; (*dsize) = (uint64_t)-1; /* As we have select not to use quotas, allways fail */ return false; } #endif /* WITH_QUOTAS */ #else /* HAVE_SYS_QUOTAS */ /* wrapper to the new sys_quota interface this file should be removed later */ bool disk_quotas(connection_struct *conn, const char *path, uint64_t *bsize, uint64_t *dfree, uint64_t *dsize) { int r; SMB_DISK_QUOTA D; unid_t id; /* * First of all, check whether user quota is * enforced. If the call fails, assume it is * not enforced. */ ZERO_STRUCT(D); id.uid = -1; r = SMB_VFS_GET_QUOTA(conn, path, SMB_USER_FS_QUOTA_TYPE, id, &D); if (r == -1 && errno != ENOSYS) { goto try_group_quota; } if (r == 0 && (D.qflags & QUOTAS_DENY_DISK) == 0) { goto try_group_quota; } ZERO_STRUCT(D); id.uid = geteuid(); r = SMB_VFS_GET_QUOTA(conn, path, SMB_USER_QUOTA_TYPE, id, &D); /* Use softlimit to determine disk space, except when it has been exceeded */ *bsize = D.bsize; if (r == -1) { if (errno == EDQUOT) { *dfree =0; *dsize =D.curblocks; return (True); } else { goto try_group_quota; } } /* Use softlimit to determine disk space, except when it has been exceeded */ if ( (D.softlimit && D.curblocks >= D.softlimit) || (D.hardlimit && D.curblocks >= D.hardlimit) || (D.isoftlimit && D.curinodes >= D.isoftlimit) || (D.ihardlimit && D.curinodes>=D.ihardlimit) ) { *dfree = 0; *dsize = D.curblocks; } else if (D.softlimit==0 && D.hardlimit==0) { goto try_group_quota; } else { if (D.softlimit == 0) { D.softlimit = D.hardlimit; } *dfree = D.softlimit - D.curblocks; *dsize = D.softlimit; } return True; try_group_quota: /* * First of all, check whether group quota is * enforced. If the call fails, assume it is * not enforced. */ ZERO_STRUCT(D); id.gid = -1; r = SMB_VFS_GET_QUOTA(conn, path, SMB_GROUP_FS_QUOTA_TYPE, id, &D); if (r == -1 && errno != ENOSYS) { return false; } if (r == 0 && (D.qflags & QUOTAS_DENY_DISK) == 0) { return false; } id.gid = getegid(); ZERO_STRUCT(D); r = SMB_VFS_GET_QUOTA(conn, path, SMB_GROUP_QUOTA_TYPE, id, &D); /* Use softlimit to determine disk space, except when it has been exceeded */ *bsize = D.bsize; if (r == -1) { if (errno == EDQUOT) { *dfree =0; *dsize =D.curblocks; return (True); } else { return False; } } /* Use softlimit to determine disk space, except when it has been exceeded */ if ( (D.softlimit && D.curblocks >= D.softlimit) || (D.hardlimit && D.curblocks >= D.hardlimit) || (D.isoftlimit && D.curinodes >= D.isoftlimit) || (D.ihardlimit && D.curinodes>=D.ihardlimit) ) { *dfree = 0; *dsize = D.curblocks; } else if (D.softlimit==0 && D.hardlimit==0) { return False; } else { if (D.softlimit == 0) { D.softlimit = D.hardlimit; } *dfree = D.softlimit - D.curblocks; *dsize = D.softlimit; } return (True); } #endif /* HAVE_SYS_QUOTAS */