xref: /illumos-gate/usr/src/uts/common/fs/smbsrv/smb_quota.c (revision 45ede40b2394db7967e59f19288fae9b62efd4aa)
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 2014 Nexenta Systems, Inc.  All rights reserved.
25  */
26 
27 #include <smbsrv/smb_kproto.h>
28 #include <smbsrv/smb_fsops.h>
29 #include <smbsrv/smb_share.h>
30 #include <smbsrv/string.h>
31 #include <sys/fs/zfs.h>
32 #include <smbsrv/smb_xdr.h>
33 #include <smbsrv/smb_door.h>
34 #include <smbsrv/smb_idmap.h>
35 
36 /*
37  * A user/group quota entry passed over the wire consists of:
38  * - next offset (uint32_t)
39  * - length of SID (uint32_t)
40  * - last modified time (uint64_t)
41  * - quota used (uint64_t)
42  * - quota limit (uint64_t)
43  * - quota threahold (uint64_t)
44  * - variable length sid - max = 32 bytes
45  * SMB_QUOTA_SIZE_NO_SID is the size of the above, excluding the sid.
46  */
47 #define	SMB_QUOTA_SIZE_NO_SID \
48 	((2 * sizeof (uint32_t)) + (4 * sizeof (uint64_t)))
49 #define	SMB_QUOTA_EST_SIZE (SMB_QUOTA_SIZE_NO_SID + SMB_EST_SID_SIZE)
50 #define	SMB_QUOTA_MAX_SIZE (SMB_QUOTA_SIZE_NO_SID + SMB_MAX_SID_SIZE)
51 
52 
53 /*
54  * smb_quota_init_sids
55  *
56  * If the query is of type SMB_QUOTA_QUERY_SIDLIST or
57  * SMB_QUOTA_QUERY_STARTSID decode the list of sids from
58  * the client request into request->qq_sid_list.
59  * Otherwise (type SMB_QUOTA_QUERY_ALL) find the resume sid
60  * and insert it into request->qq_sid_list, or reset the
61  * resume sid to NULL if request->qq_restart.
62  *
63  * Returns: NT_STATUS codes
64  */
65 uint32_t
66 smb_quota_init_sids(mbuf_chain_t *mbc, smb_quota_query_t *request,
67     smb_ofile_t *ofile)
68 {
69 	smb_quota_sid_t *sid;
70 	list_t *sid_list;
71 	uint32_t status = NT_STATUS_SUCCESS;
72 
73 	sid_list = &request->qq_sid_list;
74 	list_create(sid_list, sizeof (smb_quota_sid_t),
75 	    offsetof(smb_quota_sid_t, qs_list_node));
76 
77 	switch (request->qq_query_op) {
78 	case SMB_QUOTA_QUERY_SIDLIST:
79 	case SMB_QUOTA_QUERY_STARTSID:
80 		status = smb_quota_decode_sids(mbc, sid_list);
81 		break;
82 	case SMB_QUOTA_QUERY_ALL:
83 		if (request->qq_restart)
84 			smb_ofile_set_quota_resume(ofile, NULL);
85 		else {
86 			sid = kmem_zalloc(sizeof (smb_quota_sid_t), KM_SLEEP);
87 			list_insert_tail(sid_list, sid);
88 			smb_ofile_get_quota_resume(ofile, sid->qs_sidstr,
89 			    SMB_SID_STRSZ);
90 			if (*sid->qs_sidstr == '\0')
91 				status = NT_STATUS_INVALID_PARAMETER;
92 		}
93 		break;
94 	default:
95 		status = NT_STATUS_INVALID_PARAMETER;
96 		break;
97 	}
98 
99 	return (status);
100 }
101 
102 /*
103  * smb_quota_free_sids
104  */
105 void
106 smb_quota_free_sids(smb_quota_query_t *request)
107 {
108 	list_t *sid_list;
109 	smb_quota_sid_t *sid;
110 
111 	sid_list = &request->qq_sid_list;
112 
113 	while ((sid = list_head(sid_list)) != NULL) {
114 		list_remove(sid_list, sid);
115 		kmem_free(sid, sizeof (smb_quota_sid_t));
116 	}
117 
118 	list_destroy(sid_list);
119 }
120 
121 /*
122  * smb_quota_decode_sids
123  *
124  * Decode the SIDs from the data block and stores them in string form in list.
125  * Eaxh sid entry comprises:
126  *	next_offset (4 bytes) - offset of next entry
127  *	sid length (4 bytes)
128  *	sid (variable length = sidlen)
129  * The last entry will have a next_offset value of 0.
130  *
131  * Returns NT_STATUS codes.
132  */
133 uint32_t
134 smb_quota_decode_sids(mbuf_chain_t *mbc, list_t *list)
135 {
136 	uint32_t	offset, mb_offset, sid_offset, bytes_left;
137 	uint32_t	next_offset, sidlen;
138 	smb_sid_t	*sid;
139 	smb_quota_sid_t	*qsid;
140 	uint32_t status = NT_STATUS_SUCCESS;
141 	struct mbuf_chain sidbuf;
142 	int rc;
143 
144 	offset = 0;
145 	do {
146 		mb_offset = offset + mbc->chain_offset;
147 		bytes_left = mbc->max_bytes - mb_offset;
148 		rc = MBC_SHADOW_CHAIN(&sidbuf, mbc,
149 		    mb_offset, bytes_left);
150 		if (rc != 0) {
151 			status = NT_STATUS_INVALID_PARAMETER;
152 			break;
153 		}
154 
155 		if (smb_mbc_decodef(&sidbuf, "ll", &next_offset, &sidlen)) {
156 			status = NT_STATUS_INVALID_PARAMETER;
157 			break;
158 		}
159 
160 		sid_offset = offset + (2 * sizeof (uint32_t));
161 		sid = smb_decode_sid(mbc, sid_offset);
162 		if (sid == NULL) {
163 			status = NT_STATUS_INVALID_PARAMETER;
164 			break;
165 		}
166 
167 		qsid = kmem_zalloc(sizeof (smb_quota_sid_t), KM_SLEEP);
168 		smb_sid_tostr(sid, qsid->qs_sidstr);
169 		smb_sid_free(sid);
170 		sid = NULL;
171 
172 		list_insert_tail(list, qsid);
173 		offset += next_offset;
174 	} while ((next_offset != 0) && (bytes_left > 0));
175 
176 	return (status);
177 }
178 
179 /*
180  * smb_quota_max_quota
181  *
182  * If the query is if type SMB_QUOTA_QUERY_SIDLIST a quota entry
183  * is returned for each sid in the sidlist. request->qr_max_quota
184  * is set to 0 and is unused.
185  * Otherwise (for SMB_QUOTA_QUERY_STARTSID and SMB_QUOTA_QUERY_ALL)
186  * max_quota is the maximum number of quota entries requested from
187  * the file system (via door call smb_quota_query()).
188  * If single is set max_quota is set to 1. If single is not set
189  * max quota is calculated as the number of quotas of size
190  * SMB_QUOTA_EST_SIZE that would fit in the response buffer.
191  */
192 void
193 smb_quota_max_quota(mbuf_chain_t *mbc, smb_quota_query_t *request)
194 {
195 	if (request->qq_query_op == SMB_QUOTA_QUERY_SIDLIST)
196 		request->qq_max_quota = 0;
197 	else if (request->qq_single)
198 		request->qq_max_quota = 1;
199 	else
200 		request->qq_max_quota = (mbc->max_bytes / SMB_QUOTA_EST_SIZE);
201 }
202 
203 /*
204  * smb_quota_decode_quotas
205  *
206  * Decode the quota entries into a list_t of smb_quota_t.
207  * SMB_QUOTA_SIZE_NO_SID is the size of a quota entry,
208  * excluding the sid.
209  * The last entry will have a next_offset value of 0.
210  *
211  * Returns NT_STATUS codes.
212  */
213 uint32_t
214 smb_quota_decode_quotas(mbuf_chain_t *mbc, list_t *list)
215 {
216 	uint32_t	offset, mb_offset, sid_offset, bytes_left;
217 	uint32_t	next_offset, sidlen;
218 	uint64_t	mtime;
219 	smb_sid_t	*sid;
220 	smb_quota_t	*quota;
221 	uint32_t	status = NT_STATUS_SUCCESS;
222 	struct mbuf_chain quotabuf;
223 	int rc;
224 
225 	offset = 0;
226 	do {
227 		mb_offset = offset + mbc->chain_offset;
228 		bytes_left = mbc->max_bytes - mb_offset;
229 		rc = MBC_SHADOW_CHAIN(&quotabuf, mbc,
230 		    mb_offset, bytes_left);
231 		if (rc != 0) {
232 			status = NT_STATUS_INVALID_PARAMETER;
233 			break;
234 		}
235 
236 		quota = kmem_zalloc(sizeof (smb_quota_t), KM_SLEEP);
237 
238 		if (smb_mbc_decodef(&quotabuf, "llqqqq",
239 		    &next_offset, &sidlen, &mtime,
240 		    &quota->q_used, &quota->q_thresh, &quota->q_limit)) {
241 			kmem_free(quota, sizeof (smb_quota_t));
242 			status = NT_STATUS_INVALID_PARAMETER;
243 			break;
244 		}
245 
246 		sid_offset = offset + SMB_QUOTA_SIZE_NO_SID;
247 		sid = smb_decode_sid(mbc, sid_offset);
248 		if (sid == NULL) {
249 			kmem_free(quota, sizeof (smb_quota_t));
250 			status = NT_STATUS_INVALID_PARAMETER;
251 			break;
252 		}
253 
254 		bzero(quota->q_sidstr, SMB_SID_STRSZ);
255 		smb_sid_tostr(sid, quota->q_sidstr);
256 		smb_sid_free(sid);
257 		sid = NULL;
258 
259 		list_insert_tail(list, quota);
260 		offset += next_offset;
261 	} while ((next_offset != 0) && (bytes_left > 0));
262 
263 	return (status);
264 }
265 
266 /*
267  * smb_quota_free_quotas
268  */
269 void
270 smb_quota_free_quotas(list_t *list)
271 {
272 	smb_quota_t *quota;
273 
274 	while ((quota = list_head(list)) != NULL) {
275 		list_remove(list, quota);
276 		kmem_free(quota, sizeof (smb_quota_t));
277 	}
278 
279 	list_destroy(list);
280 }
281 
282 /*
283  * smb_quota_encode_quotas
284  *
285  * Encode the quota entries from a list_t of smb_quota_t.
286  * SMB_QUOTA_SIZE_NO_SID is the size of a quota entry,
287  * excluding the sid.
288  * The last entry will have a next_offset value of 0.
289  * Sets the last encoded SID as the resume sid.
290  */
291 uint32_t
292 smb_quota_encode_quotas(mbuf_chain_t *mbc, smb_quota_query_t *request,
293     smb_quota_response_t *reply, smb_ofile_t *ofile)
294 {
295 	uint32_t next_offset, sid_offset;
296 	uint64_t mtime = 0;
297 	uint32_t sidlen, pad;
298 	smb_sid_t *sid;
299 	char *sidstr = NULL, *resume = NULL;
300 	smb_quota_t *quota, *next_quota;
301 	list_t *list = &reply->qr_quota_list;
302 
303 	int rc;
304 	uint32_t status = NT_STATUS_SUCCESS;
305 
306 	quota = list_head(list);
307 	while (quota) {
308 		next_quota = list_next(list, quota);
309 		sidstr = quota->q_sidstr;
310 		if ((sid = smb_sid_fromstr(sidstr)) == NULL) {
311 			quota = next_quota;
312 			continue;
313 		}
314 
315 		sidlen = smb_sid_len(sid);
316 		sid_offset = SMB_QUOTA_SIZE_NO_SID;
317 		next_offset = sid_offset + sidlen;
318 		pad = smb_pad_align(next_offset, 8);
319 		next_offset += pad;
320 
321 		if (!MBC_ROOM_FOR(mbc, next_offset)) {
322 			smb_sid_free(sid);
323 			break;
324 		}
325 		if (!MBC_ROOM_FOR(mbc,
326 		    next_offset + SMB_QUOTA_MAX_SIZE)) {
327 			next_quota = NULL;
328 		}
329 
330 		rc = smb_mbc_encodef(mbc, "llqqqq",
331 		    next_quota ? next_offset : 0, sidlen, mtime,
332 		    quota->q_used, quota->q_thresh, quota->q_limit);
333 		if (rc == 0) {
334 			smb_encode_sid(mbc, sid);
335 			rc = smb_mbc_encodef(mbc, "#.", pad);
336 		}
337 
338 		smb_sid_free(sid);
339 
340 		if (rc != 0) {
341 			status = NT_STATUS_INTERNAL_ERROR;
342 			break;
343 		}
344 
345 		resume = sidstr;
346 		quota = next_quota;
347 	}
348 
349 	if ((status == NT_STATUS_SUCCESS) &&
350 	    ((request->qq_query_op == SMB_QUOTA_QUERY_STARTSID) ||
351 	    (request->qq_query_op == SMB_QUOTA_QUERY_ALL))) {
352 		smb_ofile_set_quota_resume(ofile, resume);
353 	}
354 
355 	return (status);
356 }
357 
358 /*
359  * smb_quota_query_user_quota
360  *
361  * Get user quota information for a single user (uid)
362  * for the current file system.
363  * Find the user's sid, insert it in the sidlist of a
364  * smb_quota_query_t request and invoke the door call
365  * smb_quota_query() to obtain the quota information.
366  *
367  * Returns: NT_STATUS codes.
368  */
369 uint32_t
370 smb_quota_query_user_quota(smb_request_t *sr, uid_t uid, smb_quota_t *quota)
371 {
372 	smb_sid_t *sid;
373 	smb_quota_sid_t qsid;
374 	smb_quota_query_t request;
375 	smb_quota_response_t reply;
376 	list_t *sid_list;
377 	smb_quota_t *q;
378 	smb_node_t *tnode;
379 	uint32_t status = NT_STATUS_SUCCESS;
380 
381 	if (smb_idmap_getsid(uid, SMB_IDMAP_USER, &sid) != IDMAP_SUCCESS)
382 		return (NT_STATUS_INTERNAL_ERROR);
383 
384 	smb_sid_tostr(sid, qsid.qs_sidstr);
385 	smb_sid_free(sid);
386 
387 	bzero(&request, sizeof (smb_quota_query_t));
388 	bzero(&reply, sizeof (smb_quota_response_t));
389 
390 	tnode = sr->tid_tree->t_snode;
391 	request.qq_root_path = kmem_zalloc(MAXPATHLEN, KM_SLEEP);
392 	if (smb_node_getmntpath(tnode, request.qq_root_path, MAXPATHLEN) != 0) {
393 		kmem_free(request.qq_root_path, MAXPATHLEN);
394 		return (NT_STATUS_INTERNAL_ERROR);
395 	}
396 
397 	sid_list = &request.qq_sid_list;
398 	list_create(sid_list, sizeof (smb_quota_sid_t),
399 	    offsetof(smb_quota_sid_t, qs_list_node));
400 	list_insert_tail(sid_list, &qsid);
401 
402 	request.qq_query_op = SMB_QUOTA_QUERY_SIDLIST;
403 	request.qq_single = B_TRUE;
404 
405 	if (smb_quota_query(sr->sr_server, &request, &reply) != 0) {
406 		status = NT_STATUS_INTERNAL_ERROR;
407 	} else {
408 		if (reply.qr_status != NT_STATUS_SUCCESS) {
409 			status = reply.qr_status;
410 		} else {
411 			q = list_head(&reply.qr_quota_list);
412 			if ((q == NULL) ||
413 			    (strcmp(qsid.qs_sidstr, q->q_sidstr) != 0)) {
414 				/* should never happen */
415 				status = NT_STATUS_INTERNAL_ERROR;
416 			} else {
417 				bcopy(q, quota, sizeof (smb_quota_t));
418 			}
419 		}
420 		xdr_free(smb_quota_response_xdr, (char *)&reply);
421 	}
422 
423 	kmem_free(request.qq_root_path, MAXPATHLEN);
424 	list_remove(sid_list, &qsid);
425 	list_destroy(sid_list);
426 
427 	return (status);
428 }
429 
430 /*
431  * smb_quota_query
432  *
433  * Door call to query quotas for the provided filesystem path.
434  * Returns: -1 - door call (or encode/decode) failure.
435  *	     0 - success. Status set in reply.
436  */
437 int
438 smb_quota_query(smb_server_t *sv, smb_quota_query_t *request,
439     smb_quota_response_t *reply)
440 {
441 	int	rc;
442 
443 	rc = smb_kdoor_upcall(sv, SMB_DR_QUOTA_QUERY,
444 	    request, smb_quota_query_xdr, reply, smb_quota_response_xdr);
445 
446 	return (rc);
447 }
448 
449 /*
450  * smb_quota_set
451  *
452  * Door call to set quotas for the provided filesystem path.
453  * Returns: -1 - door call (or encode/decode) failure.
454  *	     0 - success. Status set in reply.
455  */
456 int
457 smb_quota_set(smb_server_t *sv, smb_quota_set_t *request, uint32_t *reply)
458 {
459 	int	rc;
460 
461 	rc = smb_kdoor_upcall(sv, SMB_DR_QUOTA_SET,
462 	    request, smb_quota_set_xdr, reply, xdr_uint32_t);
463 
464 	return (rc);
465 }
466