1 /*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
7 *
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
12 *
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 *
19 * CDDL HEADER END
20 */
21
22 /*
23 * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
24 * Copyright 2018 Nexenta Systems, Inc. All rights reserved.
25 */
26
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <fcntl.h>
30 #include <attr.h>
31 #include <unistd.h>
32 #include <libuutil.h>
33 #include <libzfs.h>
34 #include <assert.h>
35 #include <stddef.h>
36 #include <strings.h>
37 #include <errno.h>
38 #include <synch.h>
39 #include <smbsrv/smb_xdr.h>
40 #include <smbsrv/libmlsvc.h>
41 #include <smbsrv/smb_idmap.h>
42 #include <mlsvc.h>
43 #include <sys/avl.h>
44
45 /*
46 * smb_quota subsystem interface - mlsvc.h
47 * ---------------------------------------
48 * Management of the smb_quota_fs_list (see below).
49 * smb_quota_init
50 * smb_quota_fini
51 * smb_quota_add_fs
52 * smb_quota_remove_fs
53 *
54 * smb_quota public interface - libmlsvc.h
55 * ---------------------------------------
56 * Handling of requests to query and set quota data on a filesystem.
57 * smb_quota_query - query user/group quotas on a filesystem
58 * smb_quota_set - set user/group quotas ona filesystem
59 * smb_quota_free - delete the quota list created in smb_quota_query
60 */
61
62 /*
63 * Querying user & group quotas - smb_quota_query
64 *
65 * In order to fulfill the quota query requests that can be received
66 * from clients, it is required that the quota data can be provided in
67 * a well defined and consistent order, and that a request can specify
68 * at which quota entry to begin the query.
69 *
70 * Quota Tree
71 * Since the file system does not support the above, an avl tree is
72 * populated with the file system's user and group quota data, and
73 * then used to provide the data to respond to query requests. The
74 * avl tree is indexed by the SID.
75 * Each node of the avl tree is an smb_quota_t structure.
76 *
77 * Quota List
78 * There is a list of avl trees, one per file system.
79 * Each node in the list is an smb_quota_tree_t structure.
80 * The list is created via a call to smb_quota_init() when the library
81 * is initialized, and destroyed via a call to smb_quota_fini() when
82 * the library is fini'd.
83 *
84 * An avl tree for a specific file system is created and added to the
85 * list via a call to smb_quota_add_fs() when the file system is shared,
86 * and removed from the list via a call to smb_quota_remove_fs() when
87 * the file system is unshared.
88 *
89 * An avl tree is (re)populated, if required, whenever a quota request
90 * (EXCLUDING a resume request) is received for its filesystem. The
91 * avl tree is considered to be expired (needs to be repopulated) if
92 * either of the following have occurred since it was last (re)populated:
93 * - SMB_QUOTA_REFRESH seconds have elapsed OR
94 * - a quota set operation has been performed on its file system
95 *
96 * In order to perform a smb_quota_query/set operation on a file system
97 * the appropriate quota tree must be identified and locked via a call
98 * to smb_quota_tree_lookup(), The quota tree is locked (qt_locked == B_TRUE)
99 * until the caller releases it via a call to smb_quota_tree_release().
100 */
101
102 /*
103 * smb_quota_tree_t
104 * Represents an avl tree of user quotas for a file system.
105 *
106 * qt_refcnt - a count of the number of users of the tree.
107 * qt_refcnt is also incremented and decremented when the tree is
108 * added to and removed from the quota list.
109 * The tree cannot be deleted until this count is zero.
110 *
111 * qt_sharecnt - a count of the shares of the file system which the
112 * tree represents. smb_quota_remove_fs() cannot remove the tree from
113 * removed from the quota list until this count is zero.
114 *
115 * qt_locked - B_TRUE if someone is currently using the tree, in
116 * which case a lookup will wait for the tree to become available.
117 */
118 typedef struct smb_quota_tree {
119 list_node_t qt_node;
120 char *qt_path;
121 time_t qt_timestamp;
122 uint32_t qt_refcnt;
123 uint32_t qt_sharecnt;
124 boolean_t qt_locked;
125 avl_tree_t qt_avl;
126 mutex_t qt_mutex;
127 }smb_quota_tree_t;
128
129 /*
130 * smb_quota_fs_list
131 * list of quota trees; one per shared file system.
132 */
133 static list_t smb_quota_fs_list;
134 static boolean_t smb_quota_list_init = B_FALSE;
135 static boolean_t smb_quota_shutdown = B_FALSE;
136 static mutex_t smb_quota_list_mutex = DEFAULTMUTEX;
137 static cond_t smb_quota_list_condvar;
138 static uint32_t smb_quota_tree_cnt = 0;
139 static int smb_quota_fini_timeout = 1; /* seconds */
140
141 /*
142 * smb_quota_zfs_handle_t
143 * handle to zfs library and dataset
144 */
145 typedef struct smb_quota_zfs_handle {
146 libzfs_handle_t *z_lib;
147 zfs_handle_t *z_fs;
148 } smb_quota_zfs_handle_t;
149
150 /*
151 * smb_quota_zfs_arg_t
152 * arg passed to zfs callback when querying quota properties
153 */
154 typedef struct smb_quota_zfs_arg {
155 zfs_userquota_prop_t qa_prop;
156 avl_tree_t *qa_avl;
157 } smb_quota_zfs_arg_t;
158
159 static void smb_quota_add_ctrldir(const char *);
160 static void smb_quota_remove_ctrldir(const char *);
161
162 static smb_quota_tree_t *smb_quota_tree_create(const char *);
163 static void smb_quota_tree_delete(smb_quota_tree_t *);
164
165 static smb_quota_tree_t *smb_quota_tree_lookup(const char *);
166 static void smb_quota_tree_release(smb_quota_tree_t *);
167 static boolean_t smb_quota_tree_match(smb_quota_tree_t *, const char *);
168 static int smb_quota_sid_cmp(const void *, const void *);
169 static uint32_t smb_quota_tree_populate(smb_quota_tree_t *);
170 static boolean_t smb_quota_tree_expired(smb_quota_tree_t *);
171 static void smb_quota_tree_set_expired(smb_quota_tree_t *);
172
173 static uint32_t smb_quota_zfs_init(const char *, smb_quota_zfs_handle_t *);
174 static void smb_quota_zfs_fini(smb_quota_zfs_handle_t *);
175 static uint32_t smb_quota_zfs_get_quotas(smb_quota_tree_t *);
176 static int smb_quota_zfs_callback(void *, const char *, uid_t, uint64_t);
177 static uint32_t smb_quota_zfs_set_quotas(smb_quota_tree_t *, smb_quota_set_t *);
178 static int smb_quota_sidstr(uint32_t, zfs_userquota_prop_t, char *);
179 static uint32_t smb_quota_sidtype(smb_quota_tree_t *, char *);
180 static int smb_quota_getid(char *, uint32_t, uint32_t *);
181
182 static uint32_t smb_quota_query_all(smb_quota_tree_t *,
183 smb_quota_query_t *, smb_quota_response_t *);
184 static uint32_t smb_quota_query_list(smb_quota_tree_t *,
185 smb_quota_query_t *, smb_quota_response_t *);
186
187 #define SMB_QUOTA_REFRESH 2
188 #define SMB_QUOTA_CMD_LENGTH 21
189 #define SMB_QUOTA_CMD_STR_LENGTH SMB_SID_STRSZ+SMB_QUOTA_CMD_LENGTH
190
191 /*
192 * In order to display the quota properties tab, windows clients
193 * check for the existence of the quota control file.
194 */
195 #define SMB_QUOTA_CNTRL_DIR ".$EXTEND"
196 #define SMB_QUOTA_CNTRL_FILE "$QUOTA"
197 #define SMB_QUOTA_CNTRL_INDEX_XATTR "SUNWsmb:$Q:$INDEX_ALLOCATION"
198 /*
199 * Note: this line needs to have the same format as what acl_totext() returns.
200 */
201 #define SMB_QUOTA_CNTRL_PERM "everyone@:rw-p--aARWc--s:-------:allow"
202
203 /*
204 * smb_quota_init
205 * Initialize the list to hold the quota trees.
206 */
207 void
smb_quota_init(void)208 smb_quota_init(void)
209 {
210 (void) mutex_lock(&smb_quota_list_mutex);
211 if (!smb_quota_list_init) {
212 list_create(&smb_quota_fs_list, sizeof (smb_quota_tree_t),
213 offsetof(smb_quota_tree_t, qt_node));
214 smb_quota_list_init = B_TRUE;
215 smb_quota_shutdown = B_FALSE;
216 }
217 (void) mutex_unlock(&smb_quota_list_mutex);
218 }
219
220 /*
221 * smb_quota_fini
222 *
223 * Wait for each quota tree to not be in use (qt_refcnt == 1)
224 * then remove it from the list and delete it.
225 */
226 void
smb_quota_fini(void)227 smb_quota_fini(void)
228 {
229 smb_quota_tree_t *qtree, *qtree_next;
230 boolean_t remove;
231 struct timespec tswait;
232 tswait.tv_sec = smb_quota_fini_timeout;
233 tswait.tv_nsec = 0;
234
235 (void) mutex_lock(&smb_quota_list_mutex);
236 smb_quota_shutdown = B_TRUE;
237
238 if (!smb_quota_list_init) {
239 (void) mutex_unlock(&smb_quota_list_mutex);
240 return;
241 }
242
243 (void) cond_broadcast(&smb_quota_list_condvar);
244
245 while (!list_is_empty(&smb_quota_fs_list)) {
246 qtree = list_head(&smb_quota_fs_list);
247 while (qtree != NULL) {
248 qtree_next = list_next(&smb_quota_fs_list, qtree);
249
250 (void) mutex_lock(&qtree->qt_mutex);
251 remove = (qtree->qt_refcnt == 1);
252 if (remove) {
253 list_remove(&smb_quota_fs_list, qtree);
254 --qtree->qt_refcnt;
255 }
256 (void) mutex_unlock(&qtree->qt_mutex);
257
258 if (remove)
259 smb_quota_tree_delete(qtree);
260
261 qtree = qtree_next;
262 }
263
264 if (!list_is_empty(&smb_quota_fs_list)) {
265 if (cond_reltimedwait(&smb_quota_list_condvar,
266 &smb_quota_list_mutex, &tswait) == ETIME) {
267 syslog(LOG_WARNING,
268 "quota shutdown timeout expired");
269 break;
270 }
271 }
272 }
273
274 if (list_is_empty(&smb_quota_fs_list)) {
275 list_destroy(&smb_quota_fs_list);
276 smb_quota_list_init = B_FALSE;
277 }
278
279 (void) mutex_unlock(&smb_quota_list_mutex);
280 }
281
282 /*
283 * smb_quota_add_fs
284 *
285 * If there is not a quota tree representing the specified path,
286 * create one and add it to the list.
287 */
288 void
smb_quota_add_fs(const char * path)289 smb_quota_add_fs(const char *path)
290 {
291 smb_quota_tree_t *qtree;
292
293 (void) mutex_lock(&smb_quota_list_mutex);
294
295 if (!smb_quota_list_init || smb_quota_shutdown) {
296 (void) mutex_unlock(&smb_quota_list_mutex);
297 return;
298 }
299
300 qtree = list_head(&smb_quota_fs_list);
301 while (qtree != NULL) {
302 if (smb_quota_tree_match(qtree, path)) {
303 (void) mutex_lock(&qtree->qt_mutex);
304 ++qtree->qt_sharecnt;
305 (void) mutex_unlock(&qtree->qt_mutex);
306 break;
307 }
308 qtree = list_next(&smb_quota_fs_list, qtree);
309 }
310
311 if (qtree == NULL) {
312 qtree = smb_quota_tree_create(path);
313 if (qtree)
314 list_insert_head(&smb_quota_fs_list, (void *)qtree);
315 }
316
317 if (qtree)
318 smb_quota_add_ctrldir(path);
319
320 (void) mutex_unlock(&smb_quota_list_mutex);
321 }
322
323 /*
324 * smb_quota_remove_fs
325 *
326 * If this is the last share that the quota tree represents
327 * (qtree->qt_sharecnt == 0) remove the qtree from the list.
328 * The qtree will be deleted if/when there is nobody using it
329 * (qtree->qt_refcnt == 0).
330 */
331 void
smb_quota_remove_fs(const char * path)332 smb_quota_remove_fs(const char *path)
333 {
334 smb_quota_tree_t *qtree;
335 boolean_t delete = B_FALSE;
336
337 (void) mutex_lock(&smb_quota_list_mutex);
338
339 if (!smb_quota_list_init || smb_quota_shutdown) {
340 (void) mutex_unlock(&smb_quota_list_mutex);
341 return;
342 }
343
344 qtree = list_head(&smb_quota_fs_list);
345 while (qtree != NULL) {
346 assert(qtree->qt_refcnt > 0);
347 if (smb_quota_tree_match(qtree, path)) {
348 (void) mutex_lock(&qtree->qt_mutex);
349 --qtree->qt_sharecnt;
350 if (qtree->qt_sharecnt == 0) {
351 list_remove(&smb_quota_fs_list, (void *)qtree);
352 smb_quota_remove_ctrldir(qtree->qt_path);
353 --(qtree->qt_refcnt);
354 delete = (qtree->qt_refcnt == 0);
355 }
356 (void) mutex_unlock(&qtree->qt_mutex);
357 if (delete)
358 smb_quota_tree_delete(qtree);
359 break;
360 }
361 qtree = list_next(&smb_quota_fs_list, qtree);
362 }
363 (void) mutex_unlock(&smb_quota_list_mutex);
364 }
365
366 /*
367 * smb_quota_query
368 *
369 * Get list of user/group quotas entries.
370 * Request->qq_query_op determines whether to get quota entries
371 * for the specified SIDs (smb_quota_query_list) OR to get all
372 * quota entries, optionally starting at a specified SID.
373 *
374 * Returns NT_STATUS codes.
375 */
376 uint32_t
smb_quota_query(smb_quota_query_t * request,smb_quota_response_t * reply)377 smb_quota_query(smb_quota_query_t *request, smb_quota_response_t *reply)
378 {
379 uint32_t status;
380 smb_quota_tree_t *qtree;
381 smb_quota_query_op_t query_op = request->qq_query_op;
382
383 list_create(&reply->qr_quota_list, sizeof (smb_quota_t),
384 offsetof(smb_quota_t, q_list_node));
385
386 qtree = smb_quota_tree_lookup(request->qq_root_path);
387 if (qtree == NULL)
388 return (NT_STATUS_INVALID_PARAMETER);
389
390 /* If NOT resuming a previous query all, refresh qtree if required */
391 if ((query_op != SMB_QUOTA_QUERY_ALL) || (request->qq_restart)) {
392 status = smb_quota_tree_populate(qtree);
393 if (status != NT_STATUS_SUCCESS) {
394 smb_quota_tree_release(qtree);
395 return (status);
396 }
397 }
398
399 switch (query_op) {
400 case SMB_QUOTA_QUERY_SIDLIST:
401 status = smb_quota_query_list(qtree, request, reply);
402 break;
403 case SMB_QUOTA_QUERY_STARTSID:
404 case SMB_QUOTA_QUERY_ALL:
405 status = smb_quota_query_all(qtree, request, reply);
406 break;
407 case SMB_QUOTA_QUERY_INVALID_OP:
408 default:
409 status = NT_STATUS_INVALID_PARAMETER;
410 break;
411 }
412
413 smb_quota_tree_release(qtree);
414
415 return (status);
416 }
417
418 /*
419 * smb_quota_set
420 *
421 * Set the list of quota entries.
422 */
423 uint32_t
smb_quota_set(smb_quota_set_t * request)424 smb_quota_set(smb_quota_set_t *request)
425 {
426 uint32_t status;
427 smb_quota_tree_t *qtree;
428
429 qtree = smb_quota_tree_lookup(request->qs_root_path);
430 if (qtree == NULL)
431 return (NT_STATUS_INVALID_PARAMETER);
432
433 status = smb_quota_zfs_set_quotas(qtree, request);
434
435 smb_quota_tree_set_expired(qtree);
436 smb_quota_tree_release(qtree);
437
438 return (status);
439 }
440
441 /*
442 * smb_quota_free
443 *
444 * This method frees quota entries.
445 */
446 void
smb_quota_free(smb_quota_response_t * reply)447 smb_quota_free(smb_quota_response_t *reply)
448 {
449 list_t *list = &reply->qr_quota_list;
450 smb_quota_t *quota;
451
452 while ((quota = list_head(list)) != NULL) {
453 list_remove(list, quota);
454 free(quota);
455 }
456
457 list_destroy(list);
458 }
459
460 /*
461 * smb_quota_query_all
462 *
463 * Query quotas sequentially from tree, optionally starting at a
464 * specified sid. If request->qq_single is TRUE only one quota
465 * should be returned, otherwise up to request->qq_max_quota
466 * should be returned.
467 *
468 * SMB_QUOTA_QUERY_STARTSID
469 * The query should start at the startsid, the first sid in
470 * request->qq_sid_list.
471 *
472 * SMQ_QUOTA_QUERY_ALL
473 * If request->qq_restart the query should restart at the start
474 * of the avl tree. Otherwise the first sid in request->qq_sid_list
475 * is the resume sid and the query should start at the tree entry
476 * after the one it refers to.
477 *
478 * Returns NT_STATUS codes.
479 */
480 static uint32_t
smb_quota_query_all(smb_quota_tree_t * qtree,smb_quota_query_t * request,smb_quota_response_t * reply)481 smb_quota_query_all(smb_quota_tree_t *qtree, smb_quota_query_t *request,
482 smb_quota_response_t *reply)
483 {
484 avl_tree_t *avl_tree = &qtree->qt_avl;
485 avl_index_t where;
486 list_t *sid_list, *quota_list;
487 smb_quota_sid_t *sid;
488 smb_quota_t *quota, *quotal, key;
489 uint32_t count;
490
491 /* find starting sid */
492 if (request->qq_query_op == SMB_QUOTA_QUERY_STARTSID) {
493 sid_list = &request->qq_sid_list;
494 sid = list_head(sid_list);
495 (void) strlcpy(key.q_sidstr, sid->qs_sidstr, SMB_SID_STRSZ);
496 quota = avl_find(avl_tree, &key, &where);
497 if (quota == NULL)
498 return (NT_STATUS_INVALID_PARAMETER);
499 } else if (request->qq_restart) {
500 quota = avl_first(avl_tree);
501 if (quota == NULL)
502 return (NT_STATUS_NO_MORE_ENTRIES);
503 } else {
504 sid_list = &request->qq_sid_list;
505 sid = list_head(sid_list);
506 (void) strlcpy(key.q_sidstr, sid->qs_sidstr, SMB_SID_STRSZ);
507 quota = avl_find(avl_tree, &key, &where);
508 if (quota == NULL)
509 return (NT_STATUS_INVALID_PARAMETER);
510 quota = AVL_NEXT(avl_tree, quota);
511 if (quota == NULL)
512 return (NT_STATUS_NO_MORE_ENTRIES);
513 }
514
515 if ((request->qq_single) && (request->qq_max_quota > 1))
516 request->qq_max_quota = 1;
517
518 quota_list = &reply->qr_quota_list;
519 count = 0;
520 while (quota) {
521 if (count >= request->qq_max_quota)
522 break;
523
524 quotal = malloc(sizeof (smb_quota_t));
525 if (quotal == NULL)
526 return (NT_STATUS_NO_MEMORY);
527 bcopy(quota, quotal, sizeof (smb_quota_t));
528
529 list_insert_tail(quota_list, quotal);
530 ++count;
531
532 quota = AVL_NEXT(avl_tree, quota);
533 }
534
535 return (NT_STATUS_SUCCESS);
536 }
537
538 /*
539 * smb_quota_query_list
540 *
541 * Iterate through request sid list querying the avl tree for each.
542 * Insert an entry in the reply quota list for each sid.
543 * For any sid that cannot be found in the avl tree, the reply
544 * quota list entry should contain zeros.
545 */
546 static uint32_t
smb_quota_query_list(smb_quota_tree_t * qtree,smb_quota_query_t * request,smb_quota_response_t * reply)547 smb_quota_query_list(smb_quota_tree_t *qtree, smb_quota_query_t *request,
548 smb_quota_response_t *reply)
549 {
550 avl_tree_t *avl_tree = &qtree->qt_avl;
551 avl_index_t where;
552 list_t *sid_list, *quota_list;
553 smb_quota_sid_t *sid;
554 smb_quota_t *quota, *quotal, key;
555
556 quota_list = &reply->qr_quota_list;
557 sid_list = &request->qq_sid_list;
558 sid = list_head(sid_list);
559 while (sid) {
560 quotal = malloc(sizeof (smb_quota_t));
561 if (quotal == NULL)
562 return (NT_STATUS_NO_MEMORY);
563
564 (void) strlcpy(key.q_sidstr, sid->qs_sidstr, SMB_SID_STRSZ);
565 quota = avl_find(avl_tree, &key, &where);
566 if (quota) {
567 bcopy(quota, quotal, sizeof (smb_quota_t));
568 } else {
569 bzero(quotal, sizeof (smb_quota_t));
570 (void) strlcpy(quotal->q_sidstr, sid->qs_sidstr,
571 SMB_SID_STRSZ);
572 }
573
574 list_insert_tail(quota_list, quotal);
575 sid = list_next(sid_list, sid);
576 }
577
578 return (NT_STATUS_SUCCESS);
579 }
580
581 /*
582 * smb_quota_zfs_set_quotas
583 *
584 * This method sets the list of quota entries.
585 *
586 * A quota list or threshold value of SMB_QUOTA_UNLIMITED means that
587 * the user / group does not have a quota limit. In ZFS this maps to
588 * 0 (none).
589 * A quota list or threshold value of (SMB_QUOTA_UNLIMITED - 1) means
590 * that the user / group quota should be removed. In ZFS this maps to
591 * 0 (none).
592 */
593 static uint32_t
smb_quota_zfs_set_quotas(smb_quota_tree_t * qtree,smb_quota_set_t * request)594 smb_quota_zfs_set_quotas(smb_quota_tree_t *qtree, smb_quota_set_t *request)
595 {
596 smb_quota_zfs_handle_t zfs_hdl;
597 char *typestr, qsetstr[SMB_QUOTA_CMD_STR_LENGTH];
598 char qlimit[SMB_QUOTA_CMD_LENGTH];
599 list_t *quota_list;
600 smb_quota_t *quota;
601 uint32_t id;
602 uint32_t status = NT_STATUS_SUCCESS;
603 uint32_t sidtype;
604
605 status = smb_quota_zfs_init(request->qs_root_path, &zfs_hdl);
606 if (status != NT_STATUS_SUCCESS)
607 return (status);
608
609 quota_list = &request->qs_quota_list;
610 quota = list_head(quota_list);
611
612 while (quota) {
613 if ((quota->q_limit == SMB_QUOTA_UNLIMITED) ||
614 (quota->q_limit == (SMB_QUOTA_UNLIMITED - 1))) {
615 quota->q_limit = 0;
616 }
617 (void) snprintf(qlimit, SMB_QUOTA_CMD_LENGTH, "%llu",
618 quota->q_limit);
619
620 sidtype = smb_quota_sidtype(qtree, quota->q_sidstr);
621 switch (sidtype) {
622 case SidTypeUser:
623 typestr = "userquota";
624 break;
625 case SidTypeWellKnownGroup:
626 case SidTypeGroup:
627 case SidTypeAlias:
628 typestr = "groupquota";
629 break;
630 default:
631 syslog(LOG_WARNING, "Failed to set quota for %s: "
632 "%s (%d) not valid for quotas", quota->q_sidstr,
633 smb_sid_type2str(sidtype), sidtype);
634 quota = list_next(quota_list, quota);
635 continue;
636 }
637
638 if ((smb_quota_getid(quota->q_sidstr, sidtype, &id) == 0) &&
639 !(IDMAP_ID_IS_EPHEMERAL(id))) {
640 (void) snprintf(qsetstr, SMB_QUOTA_CMD_STR_LENGTH,
641 "%s@%d", typestr, id);
642 } else {
643 (void) snprintf(qsetstr, SMB_QUOTA_CMD_STR_LENGTH,
644 "%s@%s", typestr, quota->q_sidstr);
645 }
646
647 errno = 0;
648 if (zfs_prop_set(zfs_hdl.z_fs, qsetstr, qlimit) != 0) {
649 syslog(LOG_WARNING, "Failed to set quota for %s: %s",
650 quota->q_sidstr, strerror(errno));
651 status = NT_STATUS_INVALID_PARAMETER;
652 break;
653 }
654
655 quota = list_next(quota_list, quota);
656 }
657
658 smb_quota_zfs_fini(&zfs_hdl);
659 return (status);
660 }
661
662 /*
663 * smb_quota_sidtype
664 *
665 * Determine the type of the sid. If the sid exists in
666 * the qtree get its type from there, otherwise do an
667 * lsa_lookup_sid().
668 */
669 static uint32_t
smb_quota_sidtype(smb_quota_tree_t * qtree,char * sidstr)670 smb_quota_sidtype(smb_quota_tree_t *qtree, char *sidstr)
671 {
672 smb_quota_t key, *quota;
673 avl_index_t where;
674 smb_sid_t *sid = NULL;
675 smb_account_t ainfo;
676 uint32_t sidtype = SidTypeUnknown;
677
678 (void) strlcpy(key.q_sidstr, sidstr, SMB_SID_STRSZ);
679 quota = avl_find(&qtree->qt_avl, &key, &where);
680 if (quota)
681 return (quota->q_sidtype);
682
683 sid = smb_sid_fromstr(sidstr);
684 if (sid != NULL) {
685 if (lsa_lookup_sid(sid, &ainfo) == NT_STATUS_SUCCESS) {
686 sidtype = ainfo.a_type;
687 smb_account_free(&ainfo);
688 }
689 smb_sid_free(sid);
690 }
691 return (sidtype);
692 }
693
694 /*
695 * smb_quota_getid
696 *
697 * Get the user/group id for the sid.
698 */
699 static int
smb_quota_getid(char * sidstr,uint32_t sidtype,uint32_t * id)700 smb_quota_getid(char *sidstr, uint32_t sidtype, uint32_t *id)
701 {
702 int rc = 0;
703 smb_sid_t *sid = NULL;
704 int idtype;
705
706 sid = smb_sid_fromstr(sidstr);
707 if (sid == NULL)
708 return (-1);
709
710 switch (sidtype) {
711 case SidTypeUser:
712 idtype = SMB_IDMAP_USER;
713 break;
714 case SidTypeWellKnownGroup:
715 case SidTypeGroup:
716 case SidTypeAlias:
717 idtype = SMB_IDMAP_GROUP;
718 break;
719 default:
720 rc = -1;
721 break;
722 }
723
724 if (rc == 0)
725 rc = smb_idmap_getid(sid, id, &idtype);
726
727 smb_sid_free(sid);
728
729 return (rc);
730 }
731
732 /*
733 * smb_quota_tree_lookup
734 *
735 * Find the quota tree in smb_quota_fs_list.
736 *
737 * If the tree is found but is locked, waits for it to become available.
738 * If the tree is available, locks it and returns it.
739 * Otherwise, returns NULL.
740 */
741 static smb_quota_tree_t *
smb_quota_tree_lookup(const char * path)742 smb_quota_tree_lookup(const char *path)
743 {
744 smb_quota_tree_t *qtree = NULL;
745
746 assert(path);
747 (void) mutex_lock(&smb_quota_list_mutex);
748
749 qtree = list_head(&smb_quota_fs_list);
750 while (qtree != NULL) {
751 if (!smb_quota_list_init || smb_quota_shutdown) {
752 (void) mutex_unlock(&smb_quota_list_mutex);
753 return (NULL);
754 }
755
756 (void) mutex_lock(&qtree->qt_mutex);
757 assert(qtree->qt_refcnt > 0);
758
759 if (!smb_quota_tree_match(qtree, path)) {
760 (void) mutex_unlock(&qtree->qt_mutex);
761 qtree = list_next(&smb_quota_fs_list, qtree);
762 continue;
763 }
764
765 if (qtree->qt_locked) {
766 (void) mutex_unlock(&qtree->qt_mutex);
767 (void) cond_wait(&smb_quota_list_condvar,
768 &smb_quota_list_mutex);
769 qtree = list_head(&smb_quota_fs_list);
770 continue;
771 }
772
773 ++(qtree->qt_refcnt);
774 qtree->qt_locked = B_TRUE;
775 (void) mutex_unlock(&qtree->qt_mutex);
776 break;
777 };
778
779 (void) mutex_unlock(&smb_quota_list_mutex);
780 return (qtree);
781 }
782
783 /*
784 * smb_quota_tree_release
785 */
786 static void
smb_quota_tree_release(smb_quota_tree_t * qtree)787 smb_quota_tree_release(smb_quota_tree_t *qtree)
788 {
789 boolean_t delete;
790
791 (void) mutex_lock(&qtree->qt_mutex);
792 assert(qtree->qt_locked);
793 assert(qtree->qt_refcnt > 0);
794
795 --(qtree->qt_refcnt);
796 qtree->qt_locked = B_FALSE;
797 delete = (qtree->qt_refcnt == 0);
798 (void) mutex_unlock(&qtree->qt_mutex);
799
800 (void) mutex_lock(&smb_quota_list_mutex);
801 if (delete)
802 smb_quota_tree_delete(qtree);
803 (void) cond_broadcast(&smb_quota_list_condvar);
804 (void) mutex_unlock(&smb_quota_list_mutex);
805 }
806
807 /*
808 * smb_quota_tree_match
809 *
810 * Determine if qtree represents the file system identified by path
811 */
812 static boolean_t
smb_quota_tree_match(smb_quota_tree_t * qtree,const char * path)813 smb_quota_tree_match(smb_quota_tree_t *qtree, const char *path)
814 {
815 return (strncmp(qtree->qt_path, path, MAXPATHLEN) == 0);
816 }
817
818 /*
819 * smb_quota_tree_create
820 *
821 * Create and initialize an smb_quota_tree_t structure
822 */
823 static smb_quota_tree_t *
smb_quota_tree_create(const char * path)824 smb_quota_tree_create(const char *path)
825 {
826 smb_quota_tree_t *qtree;
827
828 assert(MUTEX_HELD(&smb_quota_list_mutex));
829
830 qtree = calloc(sizeof (smb_quota_tree_t), 1);
831 if (qtree == NULL)
832 return (NULL);
833
834 qtree->qt_path = strdup(path);
835 if (qtree->qt_path == NULL) {
836 free(qtree);
837 return (NULL);
838 }
839
840 qtree->qt_timestamp = 0;
841 qtree->qt_locked = B_FALSE;
842 qtree->qt_refcnt = 1;
843 qtree->qt_sharecnt = 1;
844
845 avl_create(&qtree->qt_avl, smb_quota_sid_cmp,
846 sizeof (smb_quota_t), offsetof(smb_quota_t, q_avl_node));
847
848 ++smb_quota_tree_cnt;
849 return (qtree);
850 }
851
852 /*
853 * smb_quota_tree_delete
854 *
855 * Free and delete the smb_quota_tree_t structure.
856 * qtree must have no users (refcnt == 0).
857 */
858 static void
smb_quota_tree_delete(smb_quota_tree_t * qtree)859 smb_quota_tree_delete(smb_quota_tree_t *qtree)
860 {
861 void *cookie = NULL;
862 smb_quota_t *node;
863
864 assert(MUTEX_HELD(&smb_quota_list_mutex));
865 assert(qtree->qt_refcnt == 0);
866
867 while ((node = avl_destroy_nodes(&qtree->qt_avl, &cookie)) != NULL)
868 free(node);
869 avl_destroy(&qtree->qt_avl);
870
871 free(qtree->qt_path);
872 free(qtree);
873
874 --smb_quota_tree_cnt;
875 }
876
877 /*
878 * smb_quota_sid_cmp
879 *
880 * Comparision function for nodes in an AVL tree which holds quota
881 * entries indexed by SID.
882 */
883 static int
smb_quota_sid_cmp(const void * l_arg,const void * r_arg)884 smb_quota_sid_cmp(const void *l_arg, const void *r_arg)
885 {
886 const char *l_sid = ((smb_quota_t *)l_arg)->q_sidstr;
887 const char *r_sid = ((smb_quota_t *)r_arg)->q_sidstr;
888 int ret;
889
890 ret = strncasecmp(l_sid, r_sid, SMB_SID_STRSZ);
891
892 if (ret > 0)
893 return (1);
894 if (ret < 0)
895 return (-1);
896 return (0);
897 }
898
899 /*
900 * smb_quota_tree_populate
901 *
902 * If the quota tree needs to be (re)populated:
903 * - delete the qtree's contents
904 * - repopulate the qtree from zfs
905 * - set the qtree's timestamp.
906 */
907 static uint32_t
smb_quota_tree_populate(smb_quota_tree_t * qtree)908 smb_quota_tree_populate(smb_quota_tree_t *qtree)
909 {
910 void *cookie = NULL;
911 void *node;
912 uint32_t status;
913
914 assert(qtree->qt_locked);
915
916 if (!smb_quota_tree_expired(qtree))
917 return (NT_STATUS_SUCCESS);
918
919 while ((node = avl_destroy_nodes(&qtree->qt_avl, &cookie)) != NULL)
920 free(node);
921
922 status = smb_quota_zfs_get_quotas(qtree);
923 if (status != NT_STATUS_SUCCESS)
924 return (status);
925
926 qtree->qt_timestamp = time(NULL);
927
928 return (NT_STATUS_SUCCESS);
929 }
930
931 static boolean_t
smb_quota_tree_expired(smb_quota_tree_t * qtree)932 smb_quota_tree_expired(smb_quota_tree_t *qtree)
933 {
934 time_t tnow = time(NULL);
935 return ((tnow - qtree->qt_timestamp) > SMB_QUOTA_REFRESH);
936 }
937
938 static void
smb_quota_tree_set_expired(smb_quota_tree_t * qtree)939 smb_quota_tree_set_expired(smb_quota_tree_t *qtree)
940 {
941 qtree->qt_timestamp = 0;
942 }
943
944 /*
945 * smb_quota_zfs_get_quotas
946 *
947 * Get user and group quotas from ZFS and use them to
948 * populate the quota tree.
949 */
950 static uint32_t
smb_quota_zfs_get_quotas(smb_quota_tree_t * qtree)951 smb_quota_zfs_get_quotas(smb_quota_tree_t *qtree)
952 {
953 smb_quota_zfs_handle_t zfs_hdl;
954 smb_quota_zfs_arg_t arg;
955 zfs_userquota_prop_t p;
956 uint32_t status = NT_STATUS_SUCCESS;
957
958 status = smb_quota_zfs_init(qtree->qt_path, &zfs_hdl);
959 if (status != NT_STATUS_SUCCESS)
960 return (status);
961
962 arg.qa_avl = &qtree->qt_avl;
963 for (p = 0; p < ZFS_NUM_USERQUOTA_PROPS; p++) {
964 arg.qa_prop = p;
965 if (zfs_userspace(zfs_hdl.z_fs, p,
966 smb_quota_zfs_callback, &arg) != 0) {
967 status = NT_STATUS_INTERNAL_ERROR;
968 break;
969 }
970 }
971
972 smb_quota_zfs_fini(&zfs_hdl);
973 return (status);
974 }
975
976 /*
977 * smb_quota_zfs_callback
978 *
979 * Find or create a node in the avl tree (arg->qa_avl) that matches
980 * the SID derived from domain and rid. If no domain is specified,
981 * lookup the sid (smb_quota_sidstr()).
982 * Populate the node.
983 * The property type (arg->qa_prop) determines which property 'space'
984 * refers to.
985 */
986 static int
smb_quota_zfs_callback(void * arg,const char * domain,uid_t rid,uint64_t space)987 smb_quota_zfs_callback(void *arg, const char *domain, uid_t rid, uint64_t space)
988 {
989 smb_quota_zfs_arg_t *qarg = (smb_quota_zfs_arg_t *)arg;
990 zfs_userquota_prop_t qprop = qarg->qa_prop;
991 avl_tree_t *avl_tree = qarg->qa_avl;
992 avl_index_t where;
993 smb_quota_t *quota, key;
994
995 if (domain == NULL || domain[0] == '\0') {
996 if (smb_quota_sidstr(rid, qprop, key.q_sidstr) != 0)
997 return (0);
998 } else {
999 (void) snprintf(key.q_sidstr, SMB_SID_STRSZ, "%s-%u",
1000 domain, (uint32_t)rid);
1001 }
1002
1003 quota = avl_find(avl_tree, &key, &where);
1004 if (quota == NULL) {
1005 quota = malloc(sizeof (smb_quota_t));
1006 if (quota == NULL)
1007 return (NT_STATUS_NO_MEMORY);
1008 bzero(quota, sizeof (smb_quota_t));
1009 quota->q_thresh = SMB_QUOTA_UNLIMITED;
1010 quota->q_limit = SMB_QUOTA_UNLIMITED;
1011 avl_insert(avl_tree, (void *)quota, where);
1012 (void) strlcpy(quota->q_sidstr, key.q_sidstr, SMB_SID_STRSZ);
1013 }
1014
1015 switch (qprop) {
1016 case ZFS_PROP_USERUSED:
1017 quota->q_sidtype = SidTypeUser;
1018 quota->q_used = space;
1019 break;
1020 case ZFS_PROP_GROUPUSED:
1021 quota->q_sidtype = SidTypeGroup;
1022 quota->q_used = space;
1023 break;
1024 case ZFS_PROP_USERQUOTA:
1025 quota->q_sidtype = SidTypeUser;
1026 quota->q_limit = space;
1027 break;
1028 case ZFS_PROP_GROUPQUOTA:
1029 quota->q_sidtype = SidTypeGroup;
1030 quota->q_limit = space;
1031 break;
1032 default:
1033 break;
1034 }
1035
1036 quota->q_thresh = quota->q_limit;
1037
1038 return (0);
1039 }
1040
1041 /*
1042 * smb_quota_sidstr
1043 *
1044 * Use idmap to get the sid for the specified id and return
1045 * the string version of the sid in sidstr.
1046 * sidstr must be a buffer of at least SMB_SID_STRSZ.
1047 */
1048 static int
smb_quota_sidstr(uint32_t id,zfs_userquota_prop_t qprop,char * sidstr)1049 smb_quota_sidstr(uint32_t id, zfs_userquota_prop_t qprop, char *sidstr)
1050 {
1051 int idtype;
1052 smb_sid_t *sid;
1053
1054 switch (qprop) {
1055 case ZFS_PROP_USERUSED:
1056 case ZFS_PROP_USERQUOTA:
1057 idtype = SMB_IDMAP_USER;
1058 break;
1059 case ZFS_PROP_GROUPUSED:
1060 case ZFS_PROP_GROUPQUOTA:
1061 idtype = SMB_IDMAP_GROUP;
1062 break;
1063 default:
1064 return (-1);
1065 }
1066
1067 if (smb_idmap_getsid(id, idtype, &sid) != IDMAP_SUCCESS)
1068 return (-1);
1069
1070 smb_sid_tostr(sid, sidstr);
1071 smb_sid_free(sid);
1072
1073 return (0);
1074 }
1075
1076 /*
1077 * smb_quota_zfs_init
1078 *
1079 * Initialize zfs library and dataset handles
1080 */
1081 static uint32_t
smb_quota_zfs_init(const char * path,smb_quota_zfs_handle_t * zfs_hdl)1082 smb_quota_zfs_init(const char *path, smb_quota_zfs_handle_t *zfs_hdl)
1083 {
1084 char dataset[MAXPATHLEN];
1085
1086 if ((zfs_hdl->z_lib = libzfs_init()) == NULL)
1087 return (NT_STATUS_INTERNAL_ERROR);
1088
1089 if (smb_getdataset(zfs_hdl->z_lib, path, dataset, MAXPATHLEN) != 0) {
1090 libzfs_fini(zfs_hdl->z_lib);
1091 return (NT_STATUS_INVALID_PARAMETER);
1092 }
1093
1094 zfs_hdl->z_fs = zfs_open(zfs_hdl->z_lib, dataset, ZFS_TYPE_DATASET);
1095 if (zfs_hdl->z_fs == NULL) {
1096 libzfs_fini(zfs_hdl->z_lib);
1097 return (NT_STATUS_ACCESS_DENIED);
1098 }
1099
1100 return (NT_STATUS_SUCCESS);
1101 }
1102
1103 /*
1104 * smb_quota_zfs_fini
1105 *
1106 * Close zfs library and dataset handles
1107 */
1108 static void
smb_quota_zfs_fini(smb_quota_zfs_handle_t * zfs_hdl)1109 smb_quota_zfs_fini(smb_quota_zfs_handle_t *zfs_hdl)
1110 {
1111 zfs_close(zfs_hdl->z_fs);
1112 libzfs_fini(zfs_hdl->z_lib);
1113 }
1114
1115 /*
1116 * smb_quota_add_ctrldir
1117 *
1118 * In order to display the quota properties tab, windows clients
1119 * check for the existence of the quota control file, created
1120 * here as follows:
1121 * - Create SMB_QUOTA_CNTRL_DIR directory (with A_HIDDEN & A_SYSTEM
1122 * attributes).
1123 * - Create the SMB_QUOTA_CNTRL_FILE file (with extended attribute
1124 * SMB_QUOTA_CNTRL_INDEX_XATTR) in the SMB_QUOTA_CNTRL_DIR directory.
1125 * - Set the acl of SMB_QUOTA_CNTRL_FILE file to SMB_QUOTA_CNTRL_PERM.
1126 */
1127 static void
smb_quota_add_ctrldir(const char * path)1128 smb_quota_add_ctrldir(const char *path)
1129 {
1130 int newfd, dirfd, afd;
1131 nvlist_t *attr;
1132 char dir[MAXPATHLEN], file[MAXPATHLEN], *acl_text;
1133 acl_t *aclp, *existing_aclp;
1134 boolean_t qdir_created, prop_hidden = B_FALSE, prop_sys = B_FALSE;
1135 struct stat statbuf;
1136
1137 assert(path != NULL);
1138
1139 (void) snprintf(dir, MAXPATHLEN, ".%s/%s", path, SMB_QUOTA_CNTRL_DIR);
1140 (void) snprintf(file, MAXPATHLEN, "%s/%s", dir, SMB_QUOTA_CNTRL_FILE);
1141 if ((mkdir(dir, 0750) < 0) && (errno != EEXIST))
1142 return;
1143 qdir_created = (errno == EEXIST) ? B_FALSE : B_TRUE;
1144
1145 if ((dirfd = open(dir, O_RDONLY)) < 0) {
1146 if (qdir_created)
1147 (void) remove(dir);
1148 return;
1149 }
1150
1151 if (fgetattr(dirfd, XATTR_VIEW_READWRITE, &attr) != 0) {
1152 (void) close(dirfd);
1153 if (qdir_created)
1154 (void) remove(dir);
1155 return;
1156 }
1157
1158 if ((nvlist_lookup_boolean_value(attr, A_HIDDEN, &prop_hidden) != 0) ||
1159 (nvlist_lookup_boolean_value(attr, A_SYSTEM, &prop_sys) != 0)) {
1160 nvlist_free(attr);
1161 (void) close(dirfd);
1162 if (qdir_created)
1163 (void) remove(dir);
1164 return;
1165 }
1166 nvlist_free(attr);
1167
1168 /*
1169 * Before setting attr or acl we check if the they have already been
1170 * set to what we want. If so we could be dealing with a received
1171 * snapshot and setting these is not needed.
1172 */
1173
1174 if (!prop_hidden || !prop_sys) {
1175 if (nvlist_alloc(&attr, NV_UNIQUE_NAME, 0) == 0) {
1176 if ((nvlist_add_boolean_value(
1177 attr, A_HIDDEN, 1) != 0) ||
1178 (nvlist_add_boolean_value(
1179 attr, A_SYSTEM, 1) != 0) ||
1180 (fsetattr(dirfd, XATTR_VIEW_READWRITE, attr))) {
1181 nvlist_free(attr);
1182 (void) close(dirfd);
1183 if (qdir_created)
1184 (void) remove(dir);
1185 return;
1186 }
1187 }
1188 nvlist_free(attr);
1189 }
1190
1191 (void) close(dirfd);
1192
1193 if (stat(file, &statbuf) != 0) {
1194 if ((newfd = creat(file, 0640)) < 0) {
1195 if (qdir_created)
1196 (void) remove(dir);
1197 return;
1198 }
1199 (void) close(newfd);
1200 }
1201
1202 afd = attropen(file, SMB_QUOTA_CNTRL_INDEX_XATTR, O_RDWR | O_CREAT,
1203 0640);
1204 if (afd == -1) {
1205 (void) unlink(file);
1206 if (qdir_created)
1207 (void) remove(dir);
1208 return;
1209 }
1210 (void) close(afd);
1211
1212 if (acl_get(file, 0, &existing_aclp) == -1) {
1213 (void) unlink(file);
1214 if (qdir_created)
1215 (void) remove(dir);
1216 return;
1217 }
1218
1219 acl_text = acl_totext(existing_aclp, ACL_COMPACT_FMT);
1220 acl_free(existing_aclp);
1221 if (acl_text == NULL) {
1222 (void) unlink(file);
1223 if (qdir_created)
1224 (void) remove(dir);
1225 return;
1226 }
1227
1228 aclp = NULL;
1229 if (strcmp(acl_text, SMB_QUOTA_CNTRL_PERM) != 0) {
1230 if (acl_fromtext(SMB_QUOTA_CNTRL_PERM, &aclp) != 0) {
1231 free(acl_text);
1232 (void) unlink(file);
1233 if (qdir_created)
1234 (void) remove(dir);
1235 return;
1236 }
1237 if (acl_set(file, aclp) == -1) {
1238 free(acl_text);
1239 (void) unlink(file);
1240 if (qdir_created)
1241 (void) remove(dir);
1242 acl_free(aclp);
1243 return;
1244 }
1245 acl_free(aclp);
1246 }
1247 free(acl_text);
1248 }
1249
1250 /*
1251 * smb_quota_remove_ctrldir
1252 *
1253 * Remove SMB_QUOTA_CNTRL_FILE and SMB_QUOTA_CNTRL_DIR.
1254 */
1255 static void
smb_quota_remove_ctrldir(const char * path)1256 smb_quota_remove_ctrldir(const char *path)
1257 {
1258 char dir[MAXPATHLEN], file[MAXPATHLEN];
1259 assert(path);
1260
1261 (void) snprintf(dir, MAXPATHLEN, ".%s/%s", path, SMB_QUOTA_CNTRL_DIR);
1262 (void) snprintf(file, MAXPATHLEN, "%s/%s", dir, SMB_QUOTA_CNTRL_FILE);
1263 (void) unlink(file);
1264 (void) remove(dir);
1265 }
1266