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