Coverage Report

Created: 2025-11-16 06:57

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/samba/source3/smbd/quotas.c
Line
Count
Source
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 little 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
#endif /* Solaris */
339
340
#else /* WITH_QUOTAS */
341
342
bool disk_quotas(connection_struct *conn, struct smb_filename *fname,
343
     uint64_t *bsize, uint64_t *dfree, uint64_t *dsize)
344
{
345
  (*bsize) = 512; /* This value should be ignored */
346
347
  /* And just to be sure we set some values that hopefully */
348
  /* will be larger that any possible real-world value     */
349
  (*dfree) = (uint64_t)-1;
350
  (*dsize) = (uint64_t)-1;
351
352
  /* As we have select not to use quotas, always fail */
353
  return false;
354
}
355
#endif /* WITH_QUOTAS */
356
357
#else /* HAVE_SYS_QUOTAS */
358
/* wrapper to the new sys_quota interface
359
   this file should be removed later
360
   */
361
bool disk_quotas(connection_struct *conn, struct smb_filename *fname,
362
     uint64_t *bsize, uint64_t *dfree, uint64_t *dsize)
363
0
{
364
0
  int r;
365
0
  SMB_DISK_QUOTA D;
366
0
  unid_t id;
367
368
  /*
369
   * First of all, check whether user quota is
370
   * enforced. If the call fails, assume it is
371
   * not enforced.
372
   */
373
0
  ZERO_STRUCT(D);
374
0
  id.uid = -1;
375
0
  r = SMB_VFS_GET_QUOTA(conn, fname, SMB_USER_FS_QUOTA_TYPE,
376
0
            id, &D);
377
0
  if (r == -1 && errno != ENOSYS) {
378
0
    goto try_group_quota;
379
0
  }
380
0
  if (r == 0 && (D.qflags & QUOTAS_DENY_DISK) == 0) {
381
0
    goto try_group_quota;
382
0
  }
383
384
0
  ZERO_STRUCT(D);
385
0
  id.uid = geteuid();
386
387
  /* if new files created under this folder get this
388
   * folder's UID, then available space is governed by
389
   * the quota of the folder's UID, not the creating user.
390
   */
391
0
  if (lp_inherit_owner(SNUM(conn)) != INHERIT_OWNER_NO &&
392
0
      id.uid != fname->st.st_ex_uid && id.uid != sec_initial_uid()) {
393
0
    int save_errno;
394
395
0
    id.uid = fname->st.st_ex_uid;
396
0
    become_root();
397
0
    r = SMB_VFS_GET_QUOTA(conn, fname,
398
0
              SMB_USER_QUOTA_TYPE, id, &D);
399
0
    save_errno = errno;
400
0
    unbecome_root();
401
0
    errno = save_errno;
402
0
  } else {
403
0
    r = SMB_VFS_GET_QUOTA(conn, fname,
404
0
              SMB_USER_QUOTA_TYPE, id, &D);
405
0
  }
406
407
0
  if (r == -1) {
408
0
    goto try_group_quota;
409
0
  }
410
411
0
  *bsize = D.bsize;
412
  /* Use softlimit to determine disk space, except when it has been exceeded */
413
0
  if (
414
0
    (D.softlimit && D.curblocks >= D.softlimit) ||
415
0
    (D.hardlimit && D.curblocks >= D.hardlimit) ||
416
0
    (D.isoftlimit && D.curinodes >= D.isoftlimit) ||
417
0
    (D.ihardlimit && D.curinodes>=D.ihardlimit)
418
0
  ) {
419
0
    *dfree = 0;
420
0
    *dsize = D.curblocks;
421
0
  } else if (D.softlimit==0 && D.hardlimit==0) {
422
0
    goto try_group_quota;
423
0
  } else {
424
0
    if (D.softlimit == 0) {
425
0
      D.softlimit = D.hardlimit;
426
0
    }
427
0
    *dfree = D.softlimit - D.curblocks;
428
0
    *dsize = D.softlimit;
429
0
  }
430
431
0
  return True;
432
433
0
try_group_quota:
434
  /*
435
   * First of all, check whether group quota is
436
   * enforced. If the call fails, assume it is
437
   * not enforced.
438
   */
439
0
  ZERO_STRUCT(D);
440
0
  id.gid = -1;
441
0
  r = SMB_VFS_GET_QUOTA(conn, fname, SMB_GROUP_FS_QUOTA_TYPE,
442
0
            id, &D);
443
0
  if (r == -1 && errno != ENOSYS) {
444
0
    return false;
445
0
  }
446
0
  if (r == 0 && (D.qflags & QUOTAS_DENY_DISK) == 0) {
447
0
    return false;
448
0
  }
449
450
0
  ZERO_STRUCT(D);
451
452
  /*
453
   * If new files created under this folder get this folder's
454
   * GID, then available space is governed by the quota of the
455
   * folder's GID, not the primary group of the creating user.
456
   */
457
0
  if (VALID_STAT(fname->st) &&
458
0
      S_ISDIR(fname->st.st_ex_mode) &&
459
0
      fname->st.st_ex_mode & S_ISGID) {
460
0
    id.gid = fname->st.st_ex_gid;
461
0
    become_root();
462
0
    r = SMB_VFS_GET_QUOTA(conn, fname, SMB_GROUP_QUOTA_TYPE, id,
463
0
              &D);
464
0
    unbecome_root();
465
0
  } else {
466
0
    id.gid = getegid();
467
0
    r = SMB_VFS_GET_QUOTA(conn, fname, SMB_GROUP_QUOTA_TYPE, id,
468
0
              &D);
469
0
  }
470
471
0
  if (r == -1) {
472
0
    return False;
473
0
  }
474
475
0
  *bsize = D.bsize;
476
  /* Use softlimit to determine disk space, except when it has been exceeded */
477
0
  if (
478
0
    (D.softlimit && D.curblocks >= D.softlimit) ||
479
0
    (D.hardlimit && D.curblocks >= D.hardlimit) ||
480
0
    (D.isoftlimit && D.curinodes >= D.isoftlimit) ||
481
0
    (D.ihardlimit && D.curinodes>=D.ihardlimit)
482
0
  ) {
483
0
    *dfree = 0;
484
0
    *dsize = D.curblocks;
485
0
  } else if (D.softlimit==0 && D.hardlimit==0) {
486
0
    return False;
487
0
  } else {
488
0
    if (D.softlimit == 0) {
489
0
      D.softlimit = D.hardlimit;
490
0
    }
491
0
    *dfree = D.softlimit - D.curblocks;
492
0
    *dsize = D.softlimit;
493
0
  }
494
495
0
  return (True);
496
0
}
497
#endif /* HAVE_SYS_QUOTAS */