2f18e368b17502131c1490af55ad596d6e1e1c17
[samba.git] / source3 / smbd / quotas.c
1 /* 
2    Unix SMB/CIFS implementation.
3    support for quotas
4    Copyright (C) Andrew Tridgell 1992-1998
5    
6    This program is free software; you can redistribute it and/or modify
7    it under the terms of the GNU General Public License as published by
8    the Free Software Foundation; either version 3 of the License, or
9    (at your option) any later version.
10    
11    This program is distributed in the hope that it will be useful,
12    but WITHOUT ANY WARRANTY; without even the implied warranty of
13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14    GNU General Public License for more details.
15    
16    You should have received a copy of the GNU General Public License
17    along with this program.  If not, see <http://www.gnu.org/licenses/>.
18 */
19
20
21 /* 
22  * This is one of the most system dependent parts of Samba, and its
23  * done a litle differently. Each system has its own way of doing 
24  * things :-(
25  */
26
27 #include "includes.h"
28 #include "smbd/smbd.h"
29 #include "system/filesys.h"
30
31 #undef DBGC_CLASS
32 #define DBGC_CLASS DBGC_QUOTA
33
34 #ifndef HAVE_SYS_QUOTAS
35
36 /* just a quick hack because sysquotas.h is included before linux/quota.h */
37 #ifdef QUOTABLOCK_SIZE
38 #undef QUOTABLOCK_SIZE
39 #endif
40
41 #ifdef WITH_QUOTAS
42
43 #if defined(SUNOS5) /* Solaris */
44
45 #include <fcntl.h>
46 #include <sys/param.h>
47 #include <sys/fs/ufs_quota.h>
48 #include <sys/mnttab.h>
49 #include <sys/mntent.h>
50
51 /****************************************************************************
52  Allows querying of remote hosts for quotas on NFS mounted shares.
53  Supports normal NFS and AMD mounts.
54  Alan Romeril <a.romeril@ic.ac.uk> July 2K.
55 ****************************************************************************/
56
57 #include <rpc/rpc.h>
58 #include <rpc/types.h>
59 #include <rpcsvc/rquota.h>
60 #include <rpc/nettype.h>
61 #include <rpc/xdr.h>
62
63 static int my_xdr_getquota_rslt(XDR *xdrsp, struct getquota_rslt *gqr)
64 {
65         int quotastat;
66
67         if (!xdr_int(xdrsp, &quotastat)) {
68                 DEBUG(6,("nfs_quotas: Status bad or zero\n"));
69                 return 0;
70         }
71         gqr->status = quotastat;
72
73         if (!xdr_int(xdrsp, &gqr->getquota_rslt_u.gqr_rquota.rq_bsize)) {
74                 DEBUG(6,("nfs_quotas: Block size bad or zero\n"));
75                 return 0;
76         }
77         if (!xdr_bool(xdrsp, &gqr->getquota_rslt_u.gqr_rquota.rq_active)) {
78                 DEBUG(6,("nfs_quotas: Active bad or zero\n"));
79                 return 0;
80         }
81         if (!xdr_int(xdrsp, &gqr->getquota_rslt_u.gqr_rquota.rq_bhardlimit)) {
82                 DEBUG(6,("nfs_quotas: Hardlimit bad or zero\n"));
83                 return 0;
84         }
85         if (!xdr_int(xdrsp, &gqr->getquota_rslt_u.gqr_rquota.rq_bsoftlimit)) {
86                 DEBUG(6,("nfs_quotas: Softlimit bad or zero\n"));
87                 return 0;
88         }
89         if (!xdr_int(xdrsp, &gqr->getquota_rslt_u.gqr_rquota.rq_curblocks)) {
90                 DEBUG(6,("nfs_quotas: Currentblocks bad or zero\n"));
91                 return 0;
92         }
93         return (1);
94 }
95
96 static int my_xdr_getquota_args(XDR *xdrsp, struct getquota_args *args)
97 {
98         if (!xdr_string(xdrsp, &args->gqa_pathp, RQ_PATHLEN ))
99                 return(0);
100         if (!xdr_int(xdrsp, &args->gqa_uid))
101                 return(0);
102         return (1);
103 }
104
105 /* Restricted to SUNOS5 for the moment, I haven`t access to others to test. */
106 static bool nfs_quotas(char *nfspath, uid_t euser_id, uint64_t *bsize, uint64_t *dfree, uint64_t *dsize)
107 {
108         uid_t uid = euser_id;
109         struct dqblk D;
110         char *mnttype = nfspath;
111         CLIENT *clnt;
112         struct getquota_rslt gqr;
113         struct getquota_args args;
114         char *cutstr, *pathname, *host, *testpath;
115         int len;
116         static struct timeval timeout = {2,0};
117         enum clnt_stat clnt_stat;
118         bool ret = True;
119
120         *bsize = *dfree = *dsize = (uint64_t)0;
121
122         len=strcspn(mnttype, ":");
123         pathname=strstr(mnttype, ":");
124         cutstr = (char *) SMB_MALLOC(len+1);
125         if (!cutstr)
126                 return False;
127
128         memset(cutstr, '\0', len+1);
129         host = strncat(cutstr,mnttype, sizeof(char) * len );
130         DEBUG(5,("nfs_quotas: looking for mount on \"%s\"\n", cutstr));
131         DEBUG(5,("nfs_quotas: of path \"%s\"\n", mnttype));
132         testpath=strchr_m(mnttype, ':');
133         args.gqa_pathp = testpath+1;
134         args.gqa_uid = uid;
135
136         DEBUG(5,("nfs_quotas: Asking for host \"%s\" rpcprog \"%i\" rpcvers \"%i\" network \"%s\"\n", host, RQUOTAPROG, RQUOTAVERS, "udp"));
137
138         if ((clnt = clnt_create(host, RQUOTAPROG, RQUOTAVERS, "udp")) == NULL) {
139                 ret = False;
140                 goto out;
141         }
142
143         clnt->cl_auth = authunix_create_default();
144         DEBUG(9,("nfs_quotas: auth_success\n"));
145
146         clnt_stat=clnt_call(clnt, RQUOTAPROC_GETQUOTA, my_xdr_getquota_args, (caddr_t)&args, my_xdr_getquota_rslt, (caddr_t)&gqr, timeout);
147
148         if (clnt_stat != RPC_SUCCESS) {
149                 DEBUG(9,("nfs_quotas: clnt_call fail\n"));
150                 ret = False;
151                 goto out;
152         }
153
154         /*
155          * gqr.status returns 1 if quotas exist, 2 if there is
156          * no quota set, and 3 if no permission to get the quota.
157          * If 3, return something sensible.
158          */
159
160         switch (gqr.status) {
161         case 1:
162                 DEBUG(9,("nfs_quotas: Good quota data\n"));
163                 D.dqb_bsoftlimit = gqr.getquota_rslt_u.gqr_rquota.rq_bsoftlimit;
164                 D.dqb_bhardlimit = gqr.getquota_rslt_u.gqr_rquota.rq_bhardlimit;
165                 D.dqb_curblocks = gqr.getquota_rslt_u.gqr_rquota.rq_curblocks;
166                 break;
167
168         case 2:
169         case 3:
170                 D.dqb_bsoftlimit = 1;
171                 D.dqb_curblocks = 1;
172                 DEBUG(9,("nfs_quotas: Remote Quotas returned \"%i\" \n", gqr.status));
173                 break;
174
175         default:
176                 DEBUG(9, ("nfs_quotas: Unknown Remote Quota Status \"%i\"\n",
177                                 gqr.status));
178                 ret = false;
179                 goto out;
180         }
181
182         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",
183                         gqr.status,
184                         gqr.getquota_rslt_u.gqr_rquota.rq_bsize,
185                         gqr.getquota_rslt_u.gqr_rquota.rq_active,
186                         gqr.getquota_rslt_u.gqr_rquota.rq_bhardlimit,
187                         gqr.getquota_rslt_u.gqr_rquota.rq_bsoftlimit,
188                         gqr.getquota_rslt_u.gqr_rquota.rq_curblocks));
189
190         *bsize = gqr.getquota_rslt_u.gqr_rquota.rq_bsize;
191         *dsize = D.dqb_bsoftlimit;
192
193         if (D.dqb_curblocks > D.dqb_bsoftlimit) {
194                 *dfree = 0;
195                 *dsize = D.dqb_curblocks;
196         } else
197                 *dfree = D.dqb_bsoftlimit - D.dqb_curblocks;
198
199   out:
200
201         if (clnt) {
202                 if (clnt->cl_auth)
203                         auth_destroy(clnt->cl_auth);
204                 clnt_destroy(clnt);
205         }
206
207         DEBUG(5,("nfs_quotas: For path \"%s\" returning  bsize %.0f, dfree %.0f, dsize %.0f\n",args.gqa_pathp,(double)*bsize,(double)*dfree,(double)*dsize));
208
209         SAFE_FREE(cutstr);
210         DEBUG(10,("nfs_quotas: End of nfs_quotas\n" ));
211         return ret;
212 }
213
214 /****************************************************************************
215 try to get the disk space from disk quotas (SunOS & Solaris2 version)
216 Quota code by Peter Urbanec (amiga@cse.unsw.edu.au).
217 ****************************************************************************/
218
219 bool disk_quotas(connection_struct *conn, struct smb_filename *fname,
220                  uint64_t *bsize, uint64_t *dfree, uint64_t *dsize)
221 {
222         uid_t euser_id;
223         int ret;
224         struct dqblk D;
225         struct quotctl command;
226         int file;
227         struct mnttab mnt;
228         char *name = NULL;
229         FILE *fd;
230         SMB_STRUCT_STAT sbuf;
231         SMB_DEV_T devno;
232         bool found = false;
233         const char *path = fname->base_name;
234
235         euser_id = geteuid();
236
237         devno = fname->st.st_ex_dev;
238         DEBUG(5,("disk_quotas: looking for path \"%s\" devno=%x\n",
239                 path, (unsigned int)devno));
240         if ((fd = fopen(MNTTAB, "r")) == NULL) {
241                 return false;
242         }
243
244         while (getmntent(fd, &mnt) == 0) {
245                 if (sys_stat(mnt.mnt_mountp, &sbuf, false) == -1) {
246                         continue;
247                 }
248
249                 DEBUG(5,("disk_quotas: testing \"%s\" devno=%x\n",
250                         mnt.mnt_mountp, (unsigned int)devno));
251
252                 /* quotas are only on vxfs, UFS or NFS */
253                 if ((sbuf.st_ex_dev == devno) && (
254                         strcmp( mnt.mnt_fstype, MNTTYPE_UFS ) == 0 ||
255                         strcmp( mnt.mnt_fstype, "nfs" ) == 0    ||
256                         strcmp( mnt.mnt_fstype, "vxfs" ) == 0 )) {
257                                 found = true;
258                                 name = talloc_asprintf(talloc_tos(),
259                                                 "%s/quotas",
260                                                 mnt.mnt_mountp);
261                                 break;
262                 }
263         }
264
265         fclose(fd);
266         if (!found) {
267                 return false;
268         }
269
270         if (!name) {
271                 return false;
272         }
273         become_root();
274
275         if (strcmp(mnt.mnt_fstype, "nfs") == 0) {
276                 bool retval;
277                 DEBUG(5,("disk_quotas: looking for mountpath (NFS) \"%s\"\n",
278                                         mnt.mnt_special));
279                 retval = nfs_quotas(mnt.mnt_special,
280                                 euser_id, bsize, dfree, dsize);
281                 unbecome_root();
282                 return retval;
283         }
284
285         DEBUG(5,("disk_quotas: looking for quotas file \"%s\"\n", name));
286         if((file=open(name, O_RDONLY,0))<0) {
287                 unbecome_root();
288                 return false;
289         }
290         command.op = Q_GETQUOTA;
291         command.uid = euser_id;
292         command.addr = (caddr_t) &D;
293         ret = ioctl(file, Q_QUOTACTL, &command);
294         close(file);
295
296         unbecome_root();
297
298         if (ret < 0) {
299                 DEBUG(5,("disk_quotas ioctl (Solaris) failed. Error = %s\n",
300                                         strerror(errno) ));
301
302                 return false;
303         }
304
305         /* If softlimit is zero, set it equal to hardlimit.
306          */
307
308         if (D.dqb_bsoftlimit==0) {
309                 D.dqb_bsoftlimit = D.dqb_bhardlimit;
310         }
311
312         /* Use softlimit to determine disk space. A user exceeding the quota
313          * is told that there's no space left. Writes might actually work for
314          * a bit if the hardlimit is set higher than softlimit. Effectively
315          * the disk becomes made of rubber latex and begins to expand to
316          * accommodate the user :-)
317          */
318
319         if (D.dqb_bsoftlimit==0)
320                 return(False);
321         *bsize = DEV_BSIZE;
322         *dsize = D.dqb_bsoftlimit;
323
324         if (D.dqb_curblocks > D.dqb_bsoftlimit) {
325                 *dfree = 0;
326                 *dsize = D.dqb_curblocks;
327         } else {
328                 *dfree = D.dqb_bsoftlimit - D.dqb_curblocks;
329         }
330
331         DEBUG(5,("disk_quotas for path \"%s\" returning "
332                 "bsize %.0f, dfree %.0f, dsize %.0f\n",
333                 path,(double)*bsize,(double)*dfree,(double)*dsize));
334
335         return true;
336 }
337
338
339 #else /* not Solaris */
340
341 #if           AIX
342 /* AIX quota patch from Ole Holm Nielsen <ohnielse@fysik.dtu.dk> */
343 #include <jfs/quota.h>
344 /* AIX 4.X: Rename members of the dqblk structure (ohnielse@fysik.dtu.dk) */
345 #define dqb_curfiles dqb_curinodes
346 #define dqb_fhardlimit dqb_ihardlimit
347 #define dqb_fsoftlimit dqb_isoftlimit
348 #ifdef _AIXVERSION_530 
349 #include <sys/statfs.h>
350 #include <sys/vmount.h>
351 #endif /* AIX 5.3 */
352 #else /* !AIX */
353 #include <sys/quota.h>
354 #include <devnm.h>
355 #endif
356
357
358 /****************************************************************************
359 try to get the disk space from disk quotas - default version
360 ****************************************************************************/
361
362 bool disk_quotas(connection_struct *conn, struct smb_filename *fname,
363                  uint64_t *bsize, uint64_t *dfree, uint64_t *dsize)
364 {
365   int r;
366   struct dqblk D;
367   uid_t euser_id;
368   const char *path = fname->base_name;
369 #if !defined(AIX)
370   char dev_disk[256];
371   SMB_STRUCT_STAT S = fname->st;
372
373   /* find the block device file */
374
375   if ((sys_stat(path, &S, false)<0)
376       || (devnm(S_IFBLK, S.st_ex_dev, dev_disk, 256, 0)<0))
377         return (False);
378
379 #endif /* !defined(AIX) */
380
381   euser_id = geteuid();
382
383 #if   defined(AIX)
384   /* AIX has both USER and GROUP quotas: 
385      Get the USER quota (ohnielse@fysik.dtu.dk) */
386 #ifdef _AIXVERSION_530
387   {
388     struct statfs statbuf;
389     quota64_t user_quota;
390     if (statfs(path,&statbuf) != 0)
391       return False;
392     if(statbuf.f_vfstype == MNT_J2)
393     {
394     /* For some reason we need to be root for jfs2 */
395       become_root();
396       r = quotactl(path,QCMD(Q_J2GETQUOTA,USRQUOTA),euser_id,(char *) &user_quota);
397       unbecome_root();
398     /* Copy results to old struct to let the following code work as before */
399       D.dqb_curblocks  = user_quota.bused;
400       D.dqb_bsoftlimit = user_quota.bsoft;
401       D.dqb_bhardlimit = user_quota.bhard;
402       D.dqb_curfiles   = user_quota.iused;
403       D.dqb_fsoftlimit = user_quota.isoft;
404       D.dqb_fhardlimit = user_quota.ihard;
405     }
406     else if(statbuf.f_vfstype == MNT_JFS)
407     {
408 #endif /* AIX 5.3 */
409   save_re_uid();
410   if (set_re_uid() != 0) 
411     return False;
412   r= quotactl(path,QCMD(Q_GETQUOTA,USRQUOTA),euser_id,(char *) &D);
413   restore_re_uid();
414 #ifdef _AIXVERSION_530
415     }
416     else
417       r = 1; /* Fail for other FS-types */
418   }
419 #endif /* AIX 5.3 */
420 #else /* !AIX */
421   r=quotactl(Q_GETQUOTA, dev_disk, euser_id, &D);
422 #endif /* !AIX */
423
424   /* Use softlimit to determine disk space, except when it has been exceeded */
425   *bsize = 1024;
426
427   if (r)
428     {
429       if (errno == EDQUOT) 
430         {
431           *dfree =0;
432           *dsize =D.dqb_curblocks;
433           return (True);
434         }
435       else return(False);
436     }
437
438   /* If softlimit is zero, set it equal to hardlimit.
439    */
440
441   if (D.dqb_bsoftlimit==0)
442     D.dqb_bsoftlimit = D.dqb_bhardlimit;
443
444   if (D.dqb_bsoftlimit==0)
445     return(False);
446   /* Use softlimit to determine disk space, except when it has been exceeded */
447   if ((D.dqb_curblocks>D.dqb_bsoftlimit)
448 ||((D.dqb_curfiles>D.dqb_fsoftlimit) && (D.dqb_fsoftlimit != 0))
449     ) {
450       *dfree = 0;
451       *dsize = D.dqb_curblocks;
452     }
453   else {
454     *dfree = D.dqb_bsoftlimit - D.dqb_curblocks;
455     *dsize = D.dqb_bsoftlimit;
456   }
457   return (True);
458 }
459
460 #endif /* Solaris */
461
462 #else /* WITH_QUOTAS */
463
464 bool disk_quotas(connection_struct *conn, struct smb_filename *fname,
465                  uint64_t *bsize, uint64_t *dfree, uint64_t *dsize)
466 {
467         (*bsize) = 512; /* This value should be ignored */
468
469         /* And just to be sure we set some values that hopefully */
470         /* will be larger that any possible real-world value     */
471         (*dfree) = (uint64_t)-1;
472         (*dsize) = (uint64_t)-1;
473
474         /* As we have select not to use quotas, allways fail */
475         return false;
476 }
477 #endif /* WITH_QUOTAS */
478
479 #else /* HAVE_SYS_QUOTAS */
480 /* wrapper to the new sys_quota interface
481    this file should be removed later
482    */
483 bool disk_quotas(connection_struct *conn, struct smb_filename *fname,
484                  uint64_t *bsize, uint64_t *dfree, uint64_t *dsize)
485 {
486         int r;
487         SMB_DISK_QUOTA D;
488         unid_t id;
489
490         /*
491          * First of all, check whether user quota is
492          * enforced. If the call fails, assume it is
493          * not enforced.
494          */
495         ZERO_STRUCT(D);
496         id.uid = -1;
497         r = SMB_VFS_GET_QUOTA(conn, fname, SMB_USER_FS_QUOTA_TYPE,
498                               id, &D);
499         if (r == -1 && errno != ENOSYS) {
500                 goto try_group_quota;
501         }
502         if (r == 0 && (D.qflags & QUOTAS_DENY_DISK) == 0) {
503                 goto try_group_quota;
504         }
505
506         ZERO_STRUCT(D);
507         id.uid = geteuid();
508
509         /* if new files created under this folder get this
510          * folder's UID, then available space is governed by
511          * the quota of the folder's UID, not the creating user.
512          */
513         if (lp_inherit_owner(SNUM(conn)) != INHERIT_OWNER_NO &&
514             id.uid != fname->st.st_ex_uid && id.uid != sec_initial_uid()) {
515                 int save_errno;
516
517                 id.uid = fname->st.st_ex_uid;
518                 become_root();
519                 r = SMB_VFS_GET_QUOTA(conn, fname,
520                                       SMB_USER_QUOTA_TYPE, id, &D);
521                 save_errno = errno;
522                 unbecome_root();
523                 errno = save_errno;
524         } else {
525                 r = SMB_VFS_GET_QUOTA(conn, fname,
526                                       SMB_USER_QUOTA_TYPE, id, &D);
527         }
528
529         if (r == -1) {
530                 goto try_group_quota;
531         }
532
533         *bsize = D.bsize;
534         /* Use softlimit to determine disk space, except when it has been exceeded */
535         if (
536                 (D.softlimit && D.curblocks >= D.softlimit) ||
537                 (D.hardlimit && D.curblocks >= D.hardlimit) ||
538                 (D.isoftlimit && D.curinodes >= D.isoftlimit) ||
539                 (D.ihardlimit && D.curinodes>=D.ihardlimit)
540         ) {
541                 *dfree = 0;
542                 *dsize = D.curblocks;
543         } else if (D.softlimit==0 && D.hardlimit==0) {
544                 goto try_group_quota;
545         } else {
546                 if (D.softlimit == 0) {
547                         D.softlimit = D.hardlimit;
548                 }
549                 *dfree = D.softlimit - D.curblocks;
550                 *dsize = D.softlimit;
551         }
552
553         return True;
554         
555 try_group_quota:
556         /*
557          * First of all, check whether group quota is
558          * enforced. If the call fails, assume it is
559          * not enforced.
560          */
561         ZERO_STRUCT(D);
562         id.gid = -1;
563         r = SMB_VFS_GET_QUOTA(conn, fname, SMB_GROUP_FS_QUOTA_TYPE,
564                               id, &D);
565         if (r == -1 && errno != ENOSYS) {
566                 return false;
567         }
568         if (r == 0 && (D.qflags & QUOTAS_DENY_DISK) == 0) {
569                 return false;
570         }
571
572         id.gid = getegid();
573
574         ZERO_STRUCT(D);
575         r = SMB_VFS_GET_QUOTA(conn, fname, SMB_GROUP_QUOTA_TYPE, id,
576                               &D);
577
578         if (r == -1) {
579                 return False;
580         }
581
582         *bsize = D.bsize;
583         /* Use softlimit to determine disk space, except when it has been exceeded */
584         if (
585                 (D.softlimit && D.curblocks >= D.softlimit) ||
586                 (D.hardlimit && D.curblocks >= D.hardlimit) ||
587                 (D.isoftlimit && D.curinodes >= D.isoftlimit) ||
588                 (D.ihardlimit && D.curinodes>=D.ihardlimit)
589         ) {
590                 *dfree = 0;
591                 *dsize = D.curblocks;
592         } else if (D.softlimit==0 && D.hardlimit==0) {
593                 return False;
594         } else {
595                 if (D.softlimit == 0) {
596                         D.softlimit = D.hardlimit;
597                 }
598                 *dfree = D.softlimit - D.curblocks;
599                 *dsize = D.softlimit;
600         }
601
602         return (True);
603 }
604 #endif /* HAVE_SYS_QUOTAS */