diff options
Diffstat (limited to 'source3/smbd/quotas.c')
-rw-r--r-- | source3/smbd/quotas.c | 497 |
1 files changed, 497 insertions, 0 deletions
diff --git a/source3/smbd/quotas.c b/source3/smbd/quotas.c new file mode 100644 index 0000000..604631f --- /dev/null +++ b/source3/smbd/quotas.c @@ -0,0 +1,497 @@ +/* + 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 <http://www.gnu.org/licenses/>. +*/ + + +/* + * 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 <fcntl.h> +#include <sys/param.h> +#include <sys/fs/ufs_quota.h> +#include <sys/mnttab.h> +#include <sys/mntent.h> + +/**************************************************************************** + Allows querying of remote hosts for quotas on NFS mounted shares. + Supports normal NFS and AMD mounts. + Alan Romeril <a.romeril@ic.ac.uk> July 2K. +****************************************************************************/ + +#include <rpc/rpc.h> +#include <rpc/types.h> +#include <rpcsvc/rquota.h> +#include <rpc/nettype.h> +#include <rpc/xdr.h> + +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, struct smb_filename *fname, + 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; + const char *path = fname->base_name; + + euser_id = geteuid(); + + devno = fname->st.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; +} + +#endif /* Solaris */ + +#else /* WITH_QUOTAS */ + +bool disk_quotas(connection_struct *conn, struct smb_filename *fname, + 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, struct smb_filename *fname, + 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, fname, 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(); + + /* if new files created under this folder get this + * folder's UID, then available space is governed by + * the quota of the folder's UID, not the creating user. + */ + if (lp_inherit_owner(SNUM(conn)) != INHERIT_OWNER_NO && + id.uid != fname->st.st_ex_uid && id.uid != sec_initial_uid()) { + int save_errno; + + id.uid = fname->st.st_ex_uid; + become_root(); + r = SMB_VFS_GET_QUOTA(conn, fname, + SMB_USER_QUOTA_TYPE, id, &D); + save_errno = errno; + unbecome_root(); + errno = save_errno; + } else { + r = SMB_VFS_GET_QUOTA(conn, fname, + SMB_USER_QUOTA_TYPE, id, &D); + } + + if (r == -1) { + goto try_group_quota; + } + + *bsize = D.bsize; + /* 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, fname, 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; + } + + ZERO_STRUCT(D); + + /* + * If new files created under this folder get this folder's + * GID, then available space is governed by the quota of the + * folder's GID, not the primary group of the creating user. + */ + if (VALID_STAT(fname->st) && + S_ISDIR(fname->st.st_ex_mode) && + fname->st.st_ex_mode & S_ISGID) { + id.gid = fname->st.st_ex_gid; + become_root(); + r = SMB_VFS_GET_QUOTA(conn, fname, SMB_GROUP_QUOTA_TYPE, id, + &D); + unbecome_root(); + } else { + id.gid = getegid(); + r = SMB_VFS_GET_QUOTA(conn, fname, SMB_GROUP_QUOTA_TYPE, id, + &D); + } + + if (r == -1) { + return False; + } + + *bsize = D.bsize; + /* 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 */ |