/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, "astat)) { |
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 */ |