RFC smbd smb2 quota support (+ patches to get rid of manual marshall/unmarshall by ndr-ising the existing code)

Noel Power nopower at suse.com
Thu Mar 23 15:56:26 UTC 2017


Hi Jeremy
On 15/03/17 19:15, Noel Power wrote:
> On 15/03/17 18:52, Noel Power wrote:
>> On 13/03/17 22:08, Noel Power wrote:
>>> Hi Jeremy
>>> On 13/03/17 09:15, Noel Power wrote:
>>>> Hi Jeremy
>>>> On 10/03/17 16:50, Jeremy Allison wrote:
>>> [...]
>>>>>> I don't get it :-( afaics whatever numbers (fake or otherwise) you pump
>>>>>> into sid_list_length, start_sid_length or start_sid_offset you are going
>>>>>> to end up with an effective array len of 0 -> 4294967295, if it is too
>>>>>> big the ndr code will detect that, if it is too small then we will find
>>>>>> out about that later.
>>>>> I'm sure you're right, and I'll believe you. I still
>>>>> don't want to see *ANY* unchecked arithmetic in values
>>>>> read from the wire.
>>>> that's fine, now I get where you are coming from, I will post new
>>>> patches as soon as I rework the series
>>>>
>>>> Noel
>>> please see latest patches, hopefully they cover the issues above,
>>> additionally I added 2 more patches, one to enable smb2 for the existing
>>> quota check and the other the follow-up patch to combine
>>> build_user_quota_buffer & fill_quota_buffer into a single common func.
>> bah, I have to say please ignore these for  a moment, I spotted 2
>> problems (1 the patches all don't build individually anymore and a small
>> bug for smbcquota -C) I will post new patches later
>>
>> Noel
> hopefully better now, please see attached, basically a couple of hunks
> needed to be moved from Patch 11 to ensure Patch 8 would build.
> Additionally Patch 11 was missing a test to see if max_data was
> specified before checking if its value was exceeded
>
> thanks
>
> Noel
>
well one thing that I missed was an easy way to run list/get/set each
time I changed some detail so I've updated the patch set to include a
basic torture test for smbcquotas exercising get/set/list of quotas
using smb1 & smb2.

Also patch 14 makes a change to smbcquota to all admin users rather that
root actually set quoatas (currently only enabled if --enable-selftest)
I have to wonder though about the requirement for the user to actually
be root in order to set quotas, is that a mistake or intentional ?

thanks,

Noel

-------------- next part --------------
From 654adea2ad82523b23be137f30ff049564ee21c2 Mon Sep 17 00:00:00 2001
From: Noel Power <noel.power at suse.com>
Date: Tue, 28 Feb 2017 15:04:16 +0000
Subject: [PATCH 01/14] s3/libsmb: Avoid potential smbpanic calling
 parse_user_quota_list.

Calling parse_user_quota_list with a NULL buffer can cause a panic, while
this shouldn't happen, I managed to trigger this with an early implementation
of SMB2 quota support in smbd which didn't pass back NT_STATUS_NO_MORE_ENTRIES
when handling a SMB2_0_INFO_QUOTA GETINFO message.
OTHOH the Windows client handled the same situation gracefully.

Signed-off-by: Noel Power <noel.power at suse.com>
---
 source3/libsmb/cli_smb2_fnum.c | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/source3/libsmb/cli_smb2_fnum.c b/source3/libsmb/cli_smb2_fnum.c
index 848e077..7977bea 100644
--- a/source3/libsmb/cli_smb2_fnum.c
+++ b/source3/libsmb/cli_smb2_fnum.c
@@ -2532,6 +2532,14 @@ NTSTATUS cli_smb2_list_user_quota_step(struct cli_state *cli,
 				    ph->fid_persistent, ph->fid_volatile, frame,
 				    &outbuf);
 
+	/*
+	 * safeguard against panic from calling parse_user_quota_list with
+	 * NULL buffer
+	 */
+	if (NT_STATUS_IS_OK(status) && outbuf.length == 0) {
+		status = NT_STATUS_NO_MORE_ENTRIES;
+	}
+
 	if (!NT_STATUS_IS_OK(status)) {
 		goto cleanup;
 	}
-- 
2.10.2


From ac2ce359bf3e067c793dd076fb999f548ef5f158 Mon Sep 17 00:00:00 2001
From: Noel Power <noel.power at suse.com>
Date: Tue, 28 Feb 2017 11:36:47 +0000
Subject: [PATCH 02/14] s3/smbd: Don't stat when doing a quota operation (as
 it's a fake file)

calling SMB_VFS_STAT on the quota fake file fails and caused
FS_INFO/FileFsControlInfo request to error out early, in turn stopped a
Win8.1 client from proceeding with quota queries.

Signed-off-by: Noel Power <noel.power at suse.com>
---
 source3/smbd/trans2.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/source3/smbd/trans2.c b/source3/smbd/trans2.c
index b6bf93f..54d7c72 100644
--- a/source3/smbd/trans2.c
+++ b/source3/smbd/trans2.c
@@ -3437,7 +3437,8 @@ NTSTATUS smbd_do_qfsinfo(struct smbXsrv_connection *xconn,
 	ZERO_STRUCT(smb_fname);
 	smb_fname.base_name = discard_const_p(char, filename);
 
-	if(SMB_VFS_STAT(conn, &smb_fname) != 0) {
+	if(info_level != SMB_FS_QUOTA_INFORMATION
+	   && SMB_VFS_STAT(conn, &smb_fname) != 0) {
 		DEBUG(2,("stat of . failed (%s)\n", strerror(errno)));
 		return map_nt_error_from_unix(errno);
 	}
-- 
2.10.2


From 2c3d045364e134c755dcbbd75620e88b1936d62a Mon Sep 17 00:00:00 2001
From: Noel Power <noel.power at suse.com>
Date: Thu, 2 Mar 2017 09:20:24 +0000
Subject: [PATCH 03/14] librpc/idl Add some query [getset]info quota related
 structures

Signed-off-by: Noel Power <noel.power at suse.com>
---
 librpc/idl/quota.idl     | 54 ++++++++++++++++++++++++++++++++++++++++++++++++
 librpc/idl/wscript_build |  1 +
 librpc/wscript_build     |  5 +++++
 3 files changed, 60 insertions(+)
 create mode 100644 librpc/idl/quota.idl

diff --git a/librpc/idl/quota.idl b/librpc/idl/quota.idl
new file mode 100644
index 0000000..7c4d00a
--- /dev/null
+++ b/librpc/idl/quota.idl
@@ -0,0 +1,54 @@
+#include "idl_types.h"
+
+import "security.idl";
+
+[
+	pointer_default(unique)
+]
+
+interface file_quota {
+
+	/* MS-FSCC 2.4.33.1 */
+	typedef [public] struct {
+		uint32 next_entry_offset;
+		uint32 sid_length;
+		dom_sid sid;
+	} file_get_quota_info;
+
+	/* MS-FSCC 2.4.33 */
+	typedef [public] struct {
+		uint32 next_entry_offset;
+		uint32 sid_length;
+		hyper change_time;
+		hyper quota_used;
+		hyper quota_threshold;
+		hyper quota_limit;
+		dom_sid sid;
+	} file_quota_information;
+}
+
+interface smb2_query_quoata
+{
+	/* MS-SMB2 2.2.37.1 */
+	typedef [public] struct {
+		uint8 return_single;
+		uint8 restart_scan;
+		uint16 reserved;
+		uint32 sid_list_length;
+		uint32 start_sid_length;
+		uint32 start_sid_offset;
+	} smb2_query_quota_info;
+}
+
+interface smb1_nt_transact_query_quota
+{
+	/* MS-SMB 2.2.7.5.1 */
+	typedef [public] struct {
+		uint16 fid;
+		uint8 return_single_entry;
+		uint8 restart_scan;
+		uint32 sid_list_length;
+		uint32 start_sid_length;
+		uint32 start_sid_offset;
+	} nttrans_query_quota_params;
+}
diff --git a/librpc/idl/wscript_build b/librpc/idl/wscript_build
index 1f09ae0..346ae78 100644
--- a/librpc/idl/wscript_build
+++ b/librpc/idl/wscript_build
@@ -12,6 +12,7 @@ bld.SAMBA_PIDL_LIST('PIDL',
                        drsblobs.idl efs.idl frstrans.idl mgmt.idl netlogon.idl
                        notify.idl
                        smb2_lease_struct.idl
+                       quota.idl
                        policyagent.idl scerpc.idl svcctl.idl wkssvc.idl eventlog6.idl backupkey.idl
                        fsrvp.idl bkupblobs.idl fscc.idl frsblobs.idl witness.idl clusapi.idl
                        mdssvc.idl winspool.idl''',
diff --git a/librpc/wscript_build b/librpc/wscript_build
index 55a6f7a..6333baf 100644
--- a/librpc/wscript_build
+++ b/librpc/wscript_build
@@ -399,6 +399,11 @@ bld.SAMBA_SUBSYSTEM('NDR_SMB2_LEASE_STRUCT',
     public_headers='gen_ndr/smb2_lease_struct.h'
     )
 
+bld.SAMBA_SUBSYSTEM('NDR_QUOTA',
+    source='gen_ndr/ndr_quota.c',
+    public_deps='ndr',
+    )
+
 bld.SAMBA_SUBSYSTEM('NDR_SCHANNEL',
     source='ndr/ndr_schannel.c gen_ndr/ndr_schannel.c',
     public_deps='ndr ndr_nbt'
-- 
2.10.2


From 57eca7f8e714d5aef8368161f5ed906a9ffc578e Mon Sep 17 00:00:00 2001
From: Noel Power <noel.power at suse.com>
Date: Tue, 21 Feb 2017 11:09:08 +0000
Subject: [PATCH 04/14] s3/smbd: Add support for GetInfo/SMB2_0_INFO_QUOTA
 [MS-SMB2] 2.2.37.1 requests

Signed-off-by: Noel Power <noel.power at suse.com>
---
 source3/smbd/nttrans.c      | 369 ++++++++++++++++++++++++++++++++++++++++++++
 source3/smbd/proto.h        |  12 ++
 source3/smbd/smb2_getinfo.c |  66 +++++++-
 source3/wscript_build       |   1 +
 4 files changed, 445 insertions(+), 3 deletions(-)

diff --git a/source3/smbd/nttrans.c b/source3/smbd/nttrans.c
index a5fc625..ce5d24b 100644
--- a/source3/smbd/nttrans.c
+++ b/source3/smbd/nttrans.c
@@ -30,6 +30,8 @@
 #include "smbprofile.h"
 #include "libsmb/libsmb.h"
 #include "lib/util_ea.h"
+#include "librpc/gen_ndr/ndr_quota.h"
+#include "librpc/gen_ndr/ndr_security.h"
 
 extern const struct generic_mapping file_generic_mapping;
 
@@ -2297,6 +2299,373 @@ static void call_nt_transact_ioctl(connection_struct *conn,
 
 
 #ifdef HAVE_SYS_QUOTAS
+static enum ndr_err_code fill_qtlist_from_sids(TALLOC_CTX *mem_ctx,
+					       struct files_struct *fsp,
+					       SMB_NTQUOTA_HANDLE *qt_handle,
+					       struct dom_sid *sids,
+					       uint32_t elems)
+{
+	int i;
+	TALLOC_CTX *list_ctx = NULL;
+
+	list_ctx = talloc_init("quota_sid_list");
+
+	if (list_ctx == NULL) {
+		DBG_ERR("failed to allocate\n");
+		return NDR_ERR_ALLOC;
+	}
+
+	if (qt_handle->quota_list!=NULL) {
+		free_ntquota_list(&(qt_handle->quota_list));
+	}
+	for (i = 0; i < elems; i++) {
+		SMB_NTQUOTA_STRUCT qt;
+		SMB_NTQUOTA_LIST *list_item;
+
+		if (!NT_STATUS_IS_OK(vfs_get_ntquota(fsp,
+						     SMB_USER_QUOTA_TYPE,
+						     &sids[i], &qt))) {
+			/* non fatal error, return empty item in result */
+			ZERO_STRUCT(qt);
+			continue;
+		}
+
+
+		list_item = talloc_zero(list_ctx, SMB_NTQUOTA_LIST);
+		if (list_item == NULL) {
+			DBG_ERR("failed to allocate\n");
+			return NDR_ERR_ALLOC;
+		}
+
+		sid_to_uid(&sids[i], &list_item->uid);
+		list_item->quotas = talloc_zero(list_item, SMB_NTQUOTA_STRUCT);
+		if (list_item->quotas == NULL) {
+			DBG_ERR("failed to allocate\n");
+			return NDR_ERR_ALLOC;
+		}
+
+		*list_item->quotas = qt;
+		list_item->mem_ctx = list_ctx;
+		DLIST_ADD(qt_handle->quota_list, list_item);
+	}
+	qt_handle->tmp_list = qt_handle->quota_list;
+	return NDR_ERR_SUCCESS;
+}
+
+static enum ndr_err_code extract_sids_from_buf(TALLOC_CTX *mem_ctx,
+				  uint32_t sidlistlength,
+				  uint32_t startsidlength,
+				  uint32_t startsidoffset,
+				  DATA_BLOB *sid_buf,
+				  struct dom_sid **sids,
+				  uint32_t *num)
+{
+	DATA_BLOB blob;
+	uint32_t i = 0;
+	enum ndr_err_code err;
+
+	struct sid_list_elem {
+		struct sid_list_elem *prev, *next;
+		struct dom_sid sid;
+	};
+
+	struct sid_list_elem *sid_list = NULL;
+	struct sid_list_elem *iter = NULL;
+	TALLOC_CTX *list_ctx = talloc_init("sid_list");
+	if (!list_ctx) {
+		DBG_ERR("OOM\n");
+		err = NDR_ERR_ALLOC;
+		goto done;
+	}
+
+	*num = 0;
+	*sids = NULL;
+
+	if (sidlistlength) {
+		uint32_t offset = 0;
+		bool can_pull = true;
+		struct ndr_pull *ndr_pull = NULL;
+
+		blob.data = sid_buf->data;
+		blob.length = sidlistlength;
+		ndr_pull = ndr_pull_init_blob(&blob, mem_ctx);
+		while (can_pull) {
+			struct file_get_quota_info info;
+			struct sid_list_elem *item = NULL;
+			uint32_t new_offset = 0;
+			NDR_PULL_NEED_BYTES(ndr_pull, offset);
+			err = ndr_pull_file_get_quota_info(ndr_pull,
+					   NDR_SCALARS | NDR_BUFFERS, &info);
+			if (!NDR_ERR_CODE_IS_SUCCESS(err)) {
+				DBG_ERR("Failed to pull file_get_quota_info "
+					"from sidlist buffer\n");
+				goto done;
+			}
+			item = talloc_zero(list_ctx, struct sid_list_elem);
+			if (!item) {
+				DBG_ERR("OOM\n");
+				err = NDR_ERR_ALLOC;
+				goto done;
+			}
+			item->sid = info.sid;
+			DLIST_ADD(sid_list, item);
+			i++;
+			if (i == UINT32_MAX) {
+				DBG_ERR("Integer overflow\n");
+				err = NDR_ERR_ARRAY_SIZE;
+				goto done;
+			}
+			new_offset = info.next_entry_offset;
+			if (new_offset && new_offset <= offset) {
+				DBG_ERR("bad offset (old=0x%x, new=0x%x)\n",
+					offset,
+					new_offset);
+				err = NDR_ERR_OFFSET;
+				goto done;
+			}
+			offset = new_offset;
+			can_pull = (offset > 0);
+		}
+		*sids = talloc_zero_array(mem_ctx, struct dom_sid, i);
+		if (!sids) {
+			DBG_ERR("OOM\n");
+			err = NDR_ERR_ALLOC;
+			goto done;
+		}
+
+		*num = i;
+
+		for (iter = sid_list, i = 0; iter; iter = iter->next, i++) {
+			*sids[i] = iter->sid;
+			DBG_DEBUG("quota SID[%d] %s\n", i, sid_string_dbg(&iter->sid));
+		}
+	} else {
+		*sids = talloc_zero_array(mem_ctx, struct dom_sid, 1);
+		if (!sids) {
+			DBG_ERR("OOM\n");
+			err = NDR_ERR_ALLOC;
+			goto done;
+		}
+		err = ndr_pull_struct_blob(&blob, mem_ctx, &sids[0],
+					(ndr_pull_flags_fn_t)ndr_pull_dom_sid);
+		if (!NDR_ERR_CODE_IS_SUCCESS(err)) {
+			DBG_ERR("Failed to pull sid from sid buffer\n");
+			goto done;
+		}
+	}
+
+	err = NDR_ERR_SUCCESS;
+done:
+	TALLOC_FREE(list_ctx);
+	return err;
+}
+
+static NTSTATUS fill_quota_buffer(TALLOC_CTX *mem_ctx,
+			      SMB_NTQUOTA_HANDLE *qt_handle,
+			      bool rescan,
+			      bool return_single,
+			      uint32_t max_data,
+			      uint8_t **data,
+			      uint32_t *datalen)
+{
+	SMB_NTQUOTA_STRUCT qt;
+	SMB_NTQUOTA_LIST *tmp_list;
+	int ndr_flags = NDR_SCALARS | NDR_BUFFERS;
+	struct ndr_push *header_ndr = ndr_push_init_ctx(mem_ctx);
+	uint32_t start_offset = 0;
+	uint32_t num_pushed = 0;
+	uint32_t next_align = 0;
+	bool max_data_exceeded = false;
+
+	ZERO_STRUCT(qt);
+	*data = NULL;
+	*datalen = 0;
+
+	if (rescan) {
+		tmp_list = qt_handle->quota_list;
+	} else {
+		tmp_list = qt_handle->tmp_list;
+	}
+
+	if (tmp_list == NULL) {
+		return NT_STATUS_NO_MORE_ENTRIES;
+	}
+	for (;tmp_list!=NULL; tmp_list=tmp_list->next, num_pushed++) {
+		struct file_quota_information info;
+		enum ndr_err_code err;
+		ZERO_STRUCT(info);
+		info.sid_length = ndr_size_dom_sid(&tmp_list->quotas->sid, 0);
+
+		if (((header_ndr->offset - next_align)
+		   + sizeof(info.next_entry_offset)
+		   + sizeof(info.sid_length)
+		   + sizeof(info.change_time)
+		   + sizeof(info.quota_used)
+		   + sizeof(info.quota_threshold)
+		   + sizeof(info.quota_limit)
+		   + info.sid_length) > max_data) {
+			max_data_exceeded = true;
+			break;
+		}
+		start_offset = header_ndr->offset;
+
+		info.sid = tmp_list->quotas->sid;
+		info.quota_used = tmp_list->quotas->usedspace;
+		info.quota_threshold = tmp_list->quotas->softlim;
+		info.quota_limit = tmp_list->quotas->hardlim;
+
+		err = ndr_push_file_quota_information(header_ndr,
+						      ndr_flags,
+						      &info);
+
+		if (!NDR_ERR_CODE_IS_SUCCESS(err)) {
+			DBG_DEBUG("Failed to pull the quota sid\n");
+			return NT_STATUS_INTERNAL_ERROR;
+		}
+
+		/* pidl will align to 8 bytes due to 8 byte members*/
+		next_align = header_ndr->offset;
+		ndr_push_align(header_ndr, 8);
+		next_align = header_ndr->offset - next_align;
+		info.next_entry_offset = header_ndr->offset - start_offset;
+
+		if (return_single) {
+			break;
+		}
+
+		/* write nextentryoffset to the buffer too */
+		SIVAL(header_ndr->data, start_offset, info.next_entry_offset);
+	}
+
+
+	qt_handle->tmp_list = tmp_list;
+
+	*datalen = header_ndr->offset - next_align;
+	*data = header_ndr->data;
+
+	if (num_pushed) {
+		/* terminate the list */
+		SIVAL(header_ndr->data, start_offset, 0);
+	}
+
+	/*
+	 * if we failed to write a single file_quota_information
+	 * issue BUFFER_TOO_SMALL
+	 */
+	if (max_data_exceeded && num_pushed < 1) {
+		return NT_STATUS_BUFFER_TOO_SMALL;
+	}
+
+	return NT_STATUS_OK;
+}
+
+NTSTATUS smbd_do_query_getinfo_quota(TALLOC_CTX *mem_ctx,
+				     files_struct *fsp,
+				     struct smb2_query_quota_info *info,
+				     DATA_BLOB *sid_buf,
+				     uint32_t max_data_count,
+				     uint8_t **p_data,
+				     uint32_t *p_data_size)
+{
+	NTSTATUS status;
+	SMB_NTQUOTA_HANDLE *qt_handle = NULL;
+	uint8_t *data = NULL;
+	uint32_t data_size = 0;
+	enum ndr_err_code err;
+
+	DBG_DEBUG("sidlistlength = %d, startsidlength = %d, "
+		  "startsidoffset = %d\n",
+		  info->sid_list_length,
+		  info->start_sid_length,
+		  info->start_sid_offset);
+
+	qt_handle =
+		(SMB_NTQUOTA_HANDLE *)fsp->fake_file_handle->private_data;
+
+	if (info->sid_list_length || info->start_sid_length
+	    || info->start_sid_offset) {
+		struct dom_sid *sids;
+		uint32_t elems = 0;
+		/*
+		 * error check pulled offsets and lengths for wrap and
+		 * exceeding available bytes.
+		 */
+		if (info->sid_list_length) {
+			if (info->sid_list_length > sid_buf->length) {
+				DBG_ERR("sid_list_length 0x%x exceeds "
+					"available bytes %zx\n",
+					info->sid_list_length,
+					sid_buf->length);
+					status = NT_STATUS_INVALID_PARAMETER;
+				goto done;
+			}
+		} else {
+			if (!sid_buf->length) {
+				DBG_ERR("expected sid length but got none.\n");
+				status = NT_STATUS_INVALID_PARAMETER;
+				goto done;
+			}
+			if (info->start_sid_length + info->start_sid_offset
+			    < info->start_sid_length) {
+				DBG_ERR("integer wrap detected\n");
+				err = NDR_ERR_ARRAY_SIZE;
+				goto done;
+			}
+			if (info->start_sid_length + info->start_sid_offset
+			    > sid_buf->length) {
+				DBG_ERR("sidlistlength 0x%x + offset 0x%x "
+					"exceeds remaining bytes "
+					"in buffer %zx\n",
+					info->start_sid_length,
+					info->start_sid_offset,
+					sid_buf->length);
+				status = NT_STATUS_INVALID_PARAMETER;
+				goto done;
+			}
+		}
+
+		err = extract_sids_from_buf(mem_ctx, info->sid_list_length,
+					    info->start_sid_length,
+					    info->start_sid_offset,
+					    sid_buf, &sids, &elems);
+		if (!NDR_ERR_CODE_IS_SUCCESS(err) || elems == 0) {
+			status = NT_STATUS_INVALID_PARAMETER;
+			goto done;
+		}
+		err = fill_qtlist_from_sids(mem_ctx,
+					    fsp,
+					    qt_handle,
+					    sids,
+					    elems);
+		if (!NDR_ERR_CODE_IS_SUCCESS(err)) {
+			status = NT_STATUS_INVALID_PARAMETER;
+			goto done;
+		}
+	} else if (info->restart_scan) {
+		if (vfs_get_user_ntquota_list(fsp,
+					      &(qt_handle->quota_list))!=0) {
+			status = NT_STATUS_INTERNAL_ERROR;
+			goto done;
+		}
+	} else {
+		if (qt_handle->quota_list!=NULL &&
+			qt_handle->tmp_list==NULL) {
+			free_ntquota_list(&(qt_handle->quota_list));
+		}
+	}
+
+	status = fill_quota_buffer(mem_ctx, qt_handle,
+				   info->restart_scan !=0,
+				   info->return_single != 0,
+				   max_data_count,
+				   &data,
+				   &data_size);
+	*p_data = data;
+	*p_data_size = data_size;
+done:
+	return status;
+}
+
 /****************************************************************************
  Reply to get user quota
 ****************************************************************************/
diff --git a/source3/smbd/proto.h b/source3/smbd/proto.h
index 5671a96..b7fa16e 100644
--- a/source3/smbd/proto.h
+++ b/source3/smbd/proto.h
@@ -632,6 +632,18 @@ NTSTATUS smbd_do_query_security_desc(connection_struct *conn,
 					uint32_t max_data_count,
 					uint8_t **ppmarshalled_sd,
 					size_t *psd_size);
+#ifdef HAVE_SYS_QUOTAS
+
+struct smb2_query_quota_info;
+
+NTSTATUS smbd_do_query_getinfo_quota(TALLOC_CTX *mem_ctx,
+				     files_struct *fsp,
+				     struct smb2_query_quota_info *info,
+				     DATA_BLOB *sidbuffer,
+				     uint32_t max_data_count,
+				     uint8_t **p_data,
+				     uint32_t *p_data_size);
+#endif
 void reply_nttrans(struct smb_request *req);
 void reply_nttranss(struct smb_request *req);
 
diff --git a/source3/smbd/smb2_getinfo.c b/source3/smbd/smb2_getinfo.c
index 7f44868..d56b63c 100644
--- a/source3/smbd/smb2_getinfo.c
+++ b/source3/smbd/smb2_getinfo.c
@@ -25,6 +25,8 @@
 #include "../libcli/smb/smb_common.h"
 #include "trans2.h"
 #include "../lib/util/tevent_ntstatus.h"
+#include "librpc/gen_ndr/ndr_quota.h"
+#include "librpc/gen_ndr/ndr_security.h"
 
 static struct tevent_req *smbd_smb2_getinfo_send(TALLOC_CTX *mem_ctx,
 						 struct tevent_context *ev,
@@ -517,9 +519,67 @@ static struct tevent_req *smbd_smb2_getinfo_send(TALLOC_CTX *mem_ctx,
 		break;
 	}
 
-	case SMB2_GETINFO_QUOTA:
-		tevent_req_nterror(req, NT_STATUS_NOT_SUPPORTED);
-		return tevent_req_post(req, ev);
+	case SMB2_GETINFO_QUOTA: {
+		struct smb2_query_quota_info info;
+		enum ndr_err_code err;
+		uint8_t *data = NULL;
+		uint32_t data_size = 0;
+		struct ndr_pull *ndr_pull = NULL;
+		TALLOC_CTX *tmp_ctx = talloc_init("geninfo_quota");
+		DATA_BLOB sid_buf = data_blob_null;
+		if (!tmp_ctx) {
+			tevent_req_nterror(req, NT_STATUS_NO_MEMORY);
+			return tevent_req_post(req, ev);
+		}
+
+		ndr_pull = ndr_pull_init_blob(&in_input_buffer, tmp_ctx);
+		if (!ndr_pull) {
+			tevent_req_nterror(req, NT_STATUS_NO_MEMORY);
+			return tevent_req_post(req, ev);
+		}
+
+		err = ndr_pull_smb2_query_quota_info(ndr_pull,
+						     NDR_SCALARS | NDR_BUFFERS,
+						     &info);
+
+		if (!NDR_ERR_CODE_IS_SUCCESS(err)) {
+			DBG_DEBUG("failed to pull smb2_query_quota_info\n");
+			tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR);
+			return tevent_req_post(req, ev);
+		}
+
+		DBG_DEBUG("quota list returnsingle %d, restartscan %d, "
+			  "sid_list_length %d, start_sid_length %d, "
+			  "startsidoffset %d\n",
+			  info.return_single,
+			  info.restart_scan,
+			  info.sid_list_length,
+			  info.start_sid_length,
+			  info.start_sid_offset);
+
+		sid_buf.data = in_input_buffer.data + ndr_pull->offset;
+		sid_buf.length = in_input_buffer.length - ndr_pull->offset;
+
+		status = smbd_do_query_getinfo_quota(tmp_ctx,
+				  fsp,
+				  &info,
+				  &sid_buf,
+				  in_output_buffer_length,
+				  &data,
+				  &data_size);
+
+		if (!NT_STATUS_IS_OK(status)) {
+			TALLOC_FREE(tmp_ctx);
+			tevent_req_nterror(req, status);
+			return tevent_req_post(req, ev);
+		}
+
+		state->out_output_buffer =
+			data_blob_talloc(state, data, data_size);
+		status  = NT_STATUS_OK;
+		TALLOC_FREE(tmp_ctx);
+		break;
+	}
 
 	default:
 		DEBUG(10,("smbd_smb2_getinfo_send: "
diff --git a/source3/wscript_build b/source3/wscript_build
index c04e94a..d3f57e4 100644
--- a/source3/wscript_build
+++ b/source3/wscript_build
@@ -755,6 +755,7 @@ bld.SAMBA3_LIBRARY('smbd_base',
                         netapi
                         NDR_IOCTL
                         notifyd
+                        NDR_QUOTA
                    ''' +
                    bld.env['dmapi_lib'] +
                    bld.env['legacy_quota_libs'] +
-- 
2.10.2


From d8c614067d966ddf3773ca83a06e191bda3e60ad Mon Sep 17 00:00:00 2001
From: Noel Power <noel.power at suse.com>
Date: Wed, 1 Mar 2017 18:58:21 +0000
Subject: [PATCH 05/14] s3/smbd: Add support for SetInfo/SMB2_0_INFO_QUOTA
 [MS-SMB2] 2.2.39

Signed-off-by: Noel Power <noel.power at suse.com>
---
 source3/smbd/smb2_setinfo.c | 32 ++++++++++++++++++++++++++++++++
 1 file changed, 32 insertions(+)

diff --git a/source3/smbd/smb2_setinfo.c b/source3/smbd/smb2_setinfo.c
index db00ba0..21c8746 100644
--- a/source3/smbd/smb2_setinfo.c
+++ b/source3/smbd/smb2_setinfo.c
@@ -28,6 +28,7 @@
 #include "../librpc/gen_ndr/open_files.h"
 #include "source3/lib/dbwrap/dbwrap_watch.h"
 #include "messages.h"
+#include "librpc/gen_ndr/ndr_quota.h"
 
 static struct tevent_req *smbd_smb2_setinfo_send(TALLOC_CTX *mem_ctx,
 						 struct tevent_context *ev,
@@ -567,6 +568,37 @@ static struct tevent_req *smbd_smb2_setinfo_send(TALLOC_CTX *mem_ctx,
 		break;
 	}
 
+	case 0x04:/* SMB2_SETINFO_QUOTA */
+	{
+		struct file_quota_information info = {0};
+		SMB_NTQUOTA_STRUCT qt = {0};
+		enum ndr_err_code err;
+		if (!fsp->fake_file_handle) {
+			tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+			return tevent_req_post(req, ev);
+		}
+		err = ndr_pull_struct_blob(
+				&in_input_buffer, state, &info,
+				(ndr_pull_flags_fn_t)ndr_pull_file_quota_information);
+		if (!NDR_ERR_CODE_IS_SUCCESS(err)) {
+			tevent_req_nterror(req, NT_STATUS_UNSUCCESSFUL);
+			return tevent_req_post(req, ev);
+		}
+
+		qt.usedspace = info.quota_used;
+
+		qt.softlim = info.quota_threshold;
+
+		qt.hardlim = info.quota_limit;
+
+		qt.sid = info.sid;
+		if (vfs_set_ntquota(fsp, SMB_USER_QUOTA_TYPE, &qt.sid, &qt)!=0) {
+			tevent_req_nterror(req, NT_STATUS_UNSUCCESSFUL);
+			return tevent_req_post(req, ev);
+		}
+		status = NT_STATUS_OK;
+		break;
+	}
 	default:
 		tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
 		return tevent_req_post(req, ev);
-- 
2.10.2


From 36c5b0aee4f669fd135979b06fabc1a32b87cd0a Mon Sep 17 00:00:00 2001
From: Noel Power <noel.power at suse.com>
Date: Wed, 1 Mar 2017 18:22:48 +0000
Subject: [PATCH 06/14] s3/smbd: Use ndr generated structures and routines for
 SMB1 SET_QUOTA msg

Signed-off-by: Noel Power <noel.power at suse.com>
---
 source3/smbd/nttrans.c | 82 +++++++++++++++++++++++---------------------------
 1 file changed, 38 insertions(+), 44 deletions(-)

diff --git a/source3/smbd/nttrans.c b/source3/smbd/nttrans.c
index ce5d24b..e52fa70 100644
--- a/source3/smbd/nttrans.c
+++ b/source3/smbd/nttrans.c
@@ -2964,10 +2964,13 @@ static void call_nt_transact_set_user_quota(connection_struct *conn,
 	char *pdata = *ppdata;
 	int data_len=0,param_len=0;
 	SMB_NTQUOTA_STRUCT qt;
-	size_t sid_len;
+	struct file_quota_information info = {0};
+	enum ndr_err_code err;
 	struct dom_sid sid;
+	DATA_BLOB inblob;
 	files_struct *fsp = NULL;
-
+	TALLOC_CTX *ctx = NULL;
+	NTSTATUS status = NT_STATUS_OK;
 	ZERO_STRUCT(qt);
 
 	/* access check */
@@ -2975,8 +2978,8 @@ static void call_nt_transact_set_user_quota(connection_struct *conn,
 		DEBUG(1,("set_user_quota: access_denied service [%s] user "
 			 "[%s]\n", lp_servicename(talloc_tos(), SNUM(conn)),
 			 conn->session_info->unix_info->unix_name));
-		reply_nterror(req, NT_STATUS_ACCESS_DENIED);
-		return;
+		status = NT_STATUS_ACCESS_DENIED;
+		goto error;
 	}
 
 	/*
@@ -2985,67 +2988,58 @@ static void call_nt_transact_set_user_quota(connection_struct *conn,
 
 	if (parameter_count < 2) {
 		DEBUG(0,("TRANSACT_SET_USER_QUOTA: requires %d >= 2 bytes parameters\n",parameter_count));
-		reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
-		return;
+		status = NT_STATUS_INVALID_PARAMETER;
+		goto error;
 	}
 
 	/* maybe we can check the quota_fnum */
 	fsp = file_fsp(req, SVAL(params,0));
 	if (!check_fsp_ntquota_handle(conn, req, fsp)) {
 		DEBUG(3,("TRANSACT_GET_USER_QUOTA: no valid QUOTA HANDLE\n"));
-		reply_nterror(req, NT_STATUS_INVALID_HANDLE);
-		return;
+		status = NT_STATUS_INVALID_HANDLE;
+		goto error;
 	}
 
-	if (data_count < 40) {
-		DEBUG(0,("TRANSACT_SET_USER_QUOTA: requires %d >= %d bytes data\n",data_count,40));
-		reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
-		return;
+	ctx = talloc_init("set_user_quota");
+	if (!ctx) {
+		status = NT_STATUS_NO_MEMORY;
+		goto error;
 	}
+	inblob.data = (uint8_t*)pdata;
+	inblob.length = data_count;
 
-	/* offset to next quota record.
-	 * 4 bytes IVAL(pdata,0)
-	 * unused here...
-	 */
+	err = ndr_pull_struct_blob(
+			&inblob,
+			ctx,
+			&info,
+			(ndr_pull_flags_fn_t)ndr_pull_file_quota_information);
 
-	/* sid len */
-	sid_len = IVAL(pdata,4);
-
-	if (data_count < 40+sid_len || (40+sid_len < sid_len)) {
-		DEBUG(0,("TRANSACT_SET_USER_QUOTA: requires %d >= %lu bytes data\n",data_count,(unsigned long)40+sid_len));
-		reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
-		return;
+	if (!NDR_ERR_CODE_IS_SUCCESS(err)) {
+		DEBUG(0,("TRANSACT_SET_USER_QUOTA: failed to pull "
+			 "file_quota_information\n"));
+		status = NT_STATUS_INVALID_PARAMETER;
+		goto error;
 	}
+	qt.usedspace = info.quota_used;
 
-	/* unknown 8 bytes in pdata
-	 * maybe its the change time in NTTIME
-	 */
+	qt.softlim = info.quota_threshold;
 
-	/* the used space 8 bytes (uint64_t)*/
-	qt.usedspace = BVAL(pdata,16);
+	qt.hardlim = info.quota_limit;
 
-	/* the soft quotas 8 bytes (uint64_t)*/
-	qt.softlim = BVAL(pdata,24);
-
-	/* the hard quotas 8 bytes (uint64_t)*/
-	qt.hardlim = BVAL(pdata,32);
-
-	if (!sid_parse((const uint8_t *)(pdata+40), sid_len, &sid)) {
-		reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
-		return;
-	}
-
-	DEBUGADD(8,("SID: %s\n", sid_string_dbg(&sid)));
-
-	/* 44 unknown bytes left... */
+	sid = info.sid;
 
 	if (vfs_set_ntquota(fsp, SMB_USER_QUOTA_TYPE, &sid, &qt)!=0) {
-		reply_nterror(req, NT_STATUS_INTERNAL_ERROR);
-		return;
+		status = NT_STATUS_INTERNAL_ERROR;
+		goto error;
 	}
 
 	send_nt_replies(conn, req, NT_STATUS_OK, params, param_len,
 			pdata, data_len);
+	TALLOC_FREE(ctx);
+	return;
+error:
+	TALLOC_FREE(ctx);
+	reply_nterror(req, status);
 }
 #endif /* HAVE_SYS_QUOTAS */
 
-- 
2.10.2


From b8e71ea9ba2f36ffd09d8cc9afdbe263cb948451 Mon Sep 17 00:00:00 2001
From: Noel Power <noel.power at suse.com>
Date: Mon, 13 Mar 2017 10:22:24 +0000
Subject: [PATCH 07/14] s3/smbd: Leverage the new common code
 smbd_do_query_getinfo_quota.

Make both SMB1 & SMB2 use common code for quota queries.

Signed-off-by: Noel Power <noel.power at suse.com>
---
 source3/smbd/nttrans.c | 350 +++++++++++++++++--------------------------------
 1 file changed, 117 insertions(+), 233 deletions(-)

diff --git a/source3/smbd/nttrans.c b/source3/smbd/nttrans.c
index e52fa70..a341559 100644
--- a/source3/smbd/nttrans.c
+++ b/source3/smbd/nttrans.c
@@ -2579,6 +2579,7 @@ NTSTATUS smbd_do_query_getinfo_quota(TALLOC_CTX *mem_ctx,
 		  info->start_sid_length,
 		  info->start_sid_offset);
 
+	/* #TODO #FIXME limit/range check etc. the lenght/offset values */
 	qt_handle =
 		(SMB_NTQUOTA_HANDLE *)fsp->fake_file_handle->private_data;
 
@@ -2670,6 +2671,27 @@ done:
  Reply to get user quota
 ****************************************************************************/
 
+/*
+ * convert nttrans_query_quota_params values to conform to smb2 expectations
+ * and return struct smb2_query_quota_info to use in
+ * smbd_do_query_getinfo_quota
+ */
+static void convert_quoata_trans_param(
+		struct nttrans_query_quota_params *in,
+		struct smb2_query_quota_info *out_smb2_info)
+{
+	ZERO_STRUCTP(out_smb2_info);
+	out_smb2_info->return_single = in->return_single_entry;
+	out_smb2_info->restart_scan = in->restart_scan;
+	if (in->sid_list_length) {
+		out_smb2_info->sid_list_length = in->sid_list_length;
+		out_smb2_info->start_sid_offset = 0;
+		out_smb2_info->start_sid_length = 0;
+	} else if (in->start_sid_length) {
+		out_smb2_info->sid_list_length = in->start_sid_length;
+	}
+}
+
 static void call_nt_transact_get_user_quota(connection_struct *conn,
 					    struct smb_request *req,
 					    uint16_t **ppsetup,
@@ -2683,267 +2705,129 @@ static void call_nt_transact_get_user_quota(connection_struct *conn,
 	NTSTATUS nt_status = NT_STATUS_OK;
 	char *params = *ppparams;
 	char *pdata = *ppdata;
-	char *entry;
 	int data_len=0,param_len=0;
-	int qt_len=0;
-	int entry_len = 0;
 	files_struct *fsp = NULL;
-	uint16_t level = 0;
-	size_t sid_len;
-	struct dom_sid sid;
-	bool start_enum = True;
 	SMB_NTQUOTA_STRUCT qt;
-	SMB_NTQUOTA_LIST *tmp_list;
-	SMB_NTQUOTA_HANDLE *qt_handle = NULL;
+	DATA_BLOB blob;
+	struct nttrans_query_quota_params info;
+	enum ndr_err_code err;
+	TALLOC_CTX *tmp_ctx = NULL;
+	uint32_t resp_len = 0;
+	uint8_t *resp_data = 0;
+	struct smb2_query_quota_info info_smb2;
+	struct ndr_pull *ndr_pull = NULL;
 
 	ZERO_STRUCT(qt);
+	ZERO_STRUCT(info);
+	tmp_ctx = talloc_init("ntquota_list");
+	if (!tmp_ctx) {
+		nt_status = NT_STATUS_NO_MEMORY;
+		goto error;
+	}
 
 	/* access check */
 	if (get_current_uid(conn) != sec_initial_uid()) {
 		DEBUG(1,("get_user_quota: access_denied service [%s] user "
 			 "[%s]\n", lp_servicename(talloc_tos(), SNUM(conn)),
 			 conn->session_info->unix_info->unix_name));
-		reply_nterror(req, NT_STATUS_ACCESS_DENIED);
-		return;
+		nt_status = NT_STATUS_ACCESS_DENIED;
+		goto error;
 	}
 
-	/*
-	 * Ensure minimum number of parameters sent.
-	 */
+	blob.data = (uint8_t*)params;
+	blob.length = parameter_count;
 
-	if (parameter_count < 4) {
-		DEBUG(0,("TRANSACT_GET_USER_QUOTA: requires %d >= 4 bytes parameters\n",parameter_count));
-		reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
-		return;
-	}
+	ndr_pull = ndr_pull_init_blob(&blob, tmp_ctx);
 
-	/* maybe we can check the quota_fnum */
-	fsp = file_fsp(req, SVAL(params,0));
-	if (!check_fsp_ntquota_handle(conn, req, fsp)) {
-		DEBUG(3,("TRANSACT_GET_USER_QUOTA: no valid QUOTA HANDLE\n"));
-		reply_nterror(req, NT_STATUS_INVALID_HANDLE);
-		return;
+	if (!ndr_pull) {
+		nt_status = NT_STATUS_NO_MEMORY;
+		goto error;
 	}
 
-	/* the NULL pointer checking for fsp->fake_file_handle->pd
-	 * is done by CHECK_NTQUOTA_HANDLE_OK()
-	 */
-	qt_handle = (SMB_NTQUOTA_HANDLE *)fsp->fake_file_handle->private_data;
-
-	level = SVAL(params,2);
-
-	/* unknown 12 bytes leading in params */
-
-	switch (level) {
-		case TRANSACT_GET_USER_QUOTA_LIST_CONTINUE:
-			/* seems that we should continue with the enum here --metze */
-
-			if (qt_handle->quota_list!=NULL &&
-			    qt_handle->tmp_list==NULL) {
-
-				/* free the list */
-				free_ntquota_list(&(qt_handle->quota_list));
-
-				/* Realloc the size of parameters and data we will return */
-				param_len = 4;
-				params = nttrans_realloc(ppparams, param_len);
-				if(params == NULL) {
-					reply_nterror(req, NT_STATUS_NO_MEMORY);
-					return;
-				}
-
-				data_len = 0;
-				SIVAL(params,0,data_len);
-
-				break;
-			}
-
-			start_enum = False;
-
-		case TRANSACT_GET_USER_QUOTA_LIST_START:
-
-			if (qt_handle->quota_list==NULL &&
-				qt_handle->tmp_list==NULL) {
-				start_enum = True;
-			}
-
-			if (start_enum && vfs_get_user_ntquota_list(fsp,&(qt_handle->quota_list))!=0) {
-				reply_nterror(req, NT_STATUS_INTERNAL_ERROR);
-				return;
-			}
-
-			/* Realloc the size of parameters and data we will return */
-			param_len = 4;
-			params = nttrans_realloc(ppparams, param_len);
-			if(params == NULL) {
-				reply_nterror(req, NT_STATUS_NO_MEMORY);
-				return;
-			}
-
-			/* we should not trust the value in max_data_count*/
-			max_data_count = MIN(max_data_count,2048);
-
-			pdata = nttrans_realloc(ppdata, max_data_count);/* should be max data count from client*/
-			if(pdata == NULL) {
-				reply_nterror(req, NT_STATUS_NO_MEMORY);
-				return;
-			}
-
-			entry = pdata;
-
-			/* set params Size of returned Quota Data 4 bytes*/
-			/* but set it later when we know it */
-
-			/* for each entry push the data */
-
-			if (start_enum) {
-				qt_handle->tmp_list = qt_handle->quota_list;
-			}
-
-			tmp_list = qt_handle->tmp_list;
-
-			for (;((tmp_list!=NULL)&&((qt_len +40+SID_MAX_SIZE)<max_data_count));
-				tmp_list=tmp_list->next,entry+=entry_len,qt_len+=entry_len) {
-
-				sid_len = ndr_size_dom_sid(
-					&tmp_list->quotas->sid, 0);
-				entry_len = 40 + sid_len;
-
-				/* nextoffset entry 4 bytes */
-				SIVAL(entry,0,entry_len);
-
-				/* then the len of the SID 4 bytes */
-				SIVAL(entry,4,sid_len);
-
-				/* unknown data 8 bytes uint64_t */
-				SBIG_UINT(entry,8,(uint64_t)0); /* this is not 0 in windows...-metze*/
-
-				/* the used disk space 8 bytes uint64_t */
-				SBIG_UINT(entry,16,tmp_list->quotas->usedspace);
+	err = ndr_pull_struct_blob(&blob, tmp_ctx, &info,
+		(ndr_pull_flags_fn_t)ndr_pull_nttrans_query_quota_params);
 
-				/* the soft quotas 8 bytes uint64_t */
-				SBIG_UINT(entry,24,tmp_list->quotas->softlim);
-
-				/* the hard quotas 8 bytes uint64_t */
-				SBIG_UINT(entry,32,tmp_list->quotas->hardlim);
-
-				/* and now the SID */
-				sid_linearize((uint8_t *)(entry+40), sid_len,
-					      &tmp_list->quotas->sid);
-			}
-
-			qt_handle->tmp_list = tmp_list;
-
-			/* overwrite the offset of the last entry */
-			SIVAL(entry-entry_len,0,0);
-
-			data_len = 4+qt_len;
-			/* overwrite the params quota_data_len */
-			SIVAL(params,0,data_len);
-
-			break;
-
-		case TRANSACT_GET_USER_QUOTA_FOR_SID:
-
-			/* unknown 4 bytes IVAL(pdata,0) */
-
-			if (data_count < 8) {
-				DEBUG(0,("TRANSACT_GET_USER_QUOTA_FOR_SID: requires %d >= %d bytes data\n",data_count,8));
-				reply_nterror(req, NT_STATUS_INVALID_LEVEL);
-				return;
-			}
-
-			sid_len = IVAL(pdata,4);
-			/* Ensure this is less than 1mb. */
-			if (sid_len > (1024*1024)) {
-				reply_nterror(req, NT_STATUS_NO_MEMORY);
-				return;
-			}
-
-			if (data_count < 8+sid_len) {
-				DEBUG(0,("TRANSACT_GET_USER_QUOTA_FOR_SID: requires %d >= %lu bytes data\n",data_count,(unsigned long)(8+sid_len)));
-				reply_nterror(req, NT_STATUS_INVALID_LEVEL);
-				return;
-			}
-
-			data_len = 4+40+sid_len;
-
-			if (max_data_count < data_len) {
-				DEBUG(0,("TRANSACT_GET_USER_QUOTA_FOR_SID: max_data_count(%d) < data_len(%d)\n",
-					max_data_count, data_len));
-				param_len = 4;
-				SIVAL(params,0,data_len);
-				data_len = 0;
-				nt_status = NT_STATUS_BUFFER_TOO_SMALL;
-				break;
-			}
-
-			if (!sid_parse((const uint8_t *)(pdata+8), sid_len,
-				       &sid)) {
-				reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
-				return;
-			}
-
-			nt_status = vfs_get_ntquota(fsp, SMB_USER_QUOTA_TYPE,
-						    &sid, &qt);
-			if (!NT_STATUS_IS_OK(nt_status)) {
-				reply_nterror(req, nt_status);
-				return;
-			}
-
-			/* Realloc the size of parameters and data we will return */
-			param_len = 4;
-			params = nttrans_realloc(ppparams, param_len);
-			if(params == NULL) {
-				reply_nterror(req, NT_STATUS_NO_MEMORY);
-				return;
-			}
-
-			pdata = nttrans_realloc(ppdata, data_len);
-			if(pdata == NULL) {
-				reply_nterror(req, NT_STATUS_NO_MEMORY);
-				return;
-			}
-
-			entry = pdata;
-
-			/* set params Size of returned Quota Data 4 bytes*/
-			SIVAL(params,0,data_len);
-
-			/* nextoffset entry 4 bytes */
-			SIVAL(entry,0,0);
-
-			/* then the len of the SID 4 bytes */
-			SIVAL(entry,4,sid_len);
+	if (!NDR_ERR_CODE_IS_SUCCESS(err)) {
+		DEBUG(0,("TRANSACT_GET_USER_QUOTA: failed to pull "
+			 "query_quota_params."));
+		nt_status = NT_STATUS_INVALID_PARAMETER;
+		goto error;
+	}
+	DBG_DEBUG("info.return_single_entry = %d, info.restart_scan = %d, "
+		  "info.sid_list_length = %d, info.start_sid_length = %d, "
+		  "info.start_sid_offset = %d\n",
+		  info.return_single_entry,
+		  info.restart_scan,
+		  info.sid_list_length,
+		  info.start_sid_length,
+		  info.start_sid_offset);
+
+
+	if (info.start_sid_length && info.start_sid_offset) {
+		if (info.start_sid_offset > data_count) {
+			DBG_ERR("TRANSACT_GET_USER_QUOTA: illegal sid_offset "
+				"0x%x into sid buffer (of length 0x%x)\n",
+				info.start_sid_offset, (uint32_t)blob.length);
+			nt_status = NT_STATUS_INVALID_PARAMETER;
+			goto error;
+		}
+		blob.data = (uint8_t*)pdata + info.start_sid_offset ;
+	} else {
+		blob.data = (uint8_t*)pdata + ndr_pull->offset;
+	}
 
-			/* unknown data 8 bytes uint64_t */
-			SBIG_UINT(entry,8,(uint64_t)0); /* this is not 0 in windows...-mezte*/
+	blob.length = data_count;
 
-			/* the used disk space 8 bytes uint64_t */
-			SBIG_UINT(entry,16,qt.usedspace);
+	/* populate smb2 equivelant structure */
+	convert_quoata_trans_param(&info, &info_smb2);
 
-			/* the soft quotas 8 bytes uint64_t */
-			SBIG_UINT(entry,24,qt.softlim);
+	/* maybe we can check the quota_fnum */
+	fsp = file_fsp(req, info.fid);
+	if (!check_fsp_ntquota_handle(conn, req, fsp)) {
+		DEBUG(3,("TRANSACT_GET_USER_QUOTA: no valid QUOTA HANDLE\n"));
+		nt_status = NT_STATUS_INVALID_HANDLE;
+		goto error;
+	}
+	nt_status = smbd_do_query_getinfo_quota(tmp_ctx,
+				  fsp,
+				  &info_smb2,
+				  &blob,
+				  max_data_count,
+				  &resp_data,
+				  &resp_len);
+	if (!NT_STATUS_IS_OK(nt_status)) {
+		/*
+		 * smb1 doesn't send NT_STATUS_NO_MORE_ENTRIES so swallow
+		 * this status.
+		 */
+		if (NT_STATUS_EQUAL(nt_status, NT_STATUS_NO_MORE_ENTRIES)) {
+			nt_status = NT_STATUS_OK;
+		} else {
+			goto error;
+		}
+	}
 
-			/* the hard quotas 8 bytes uint64_t */
-			SBIG_UINT(entry,32,qt.hardlim);
+	param_len = 4;
+	params = nttrans_realloc(ppparams, param_len);
+	if(params == NULL) {
+		nt_status = NT_STATUS_NO_MEMORY;
+		goto error;
+	}
 
-			/* and now the SID */
-			sid_linearize((uint8_t *)(entry+40), sid_len, &sid);
+	SIVAL(params, 0, param_len);
 
-			break;
+	data_len = resp_len;
+	pdata = nttrans_realloc(ppdata, data_len);
 
-		default:
-			DEBUG(0, ("do_nt_transact_get_user_quota: %s: unknown "
-				  "level 0x%04hX\n",
-				  fsp_fnum_dbg(fsp), level));
-			reply_nterror(req, NT_STATUS_INVALID_LEVEL);
-			return;
-			break;
-	}
+	memcpy(pdata, resp_data, data_len);
 
+	TALLOC_FREE(tmp_ctx);
 	send_nt_replies(conn, req, nt_status, params, param_len,
 			pdata, data_len);
+	return;
+error:
+	TALLOC_FREE(tmp_ctx);
+	reply_nterror(req, nt_status);
 }
 
 /****************************************************************************
-- 
2.10.2


From 876d99f4e1972899451b15150c8820ff030de740 Mon Sep 17 00:00:00 2001
From: Noel Power <noel.power at suse.com>
Date: Wed, 1 Mar 2017 14:19:14 +0000
Subject: [PATCH 08/14] s3/libsmb: Use ndr generated structures & routines for
 SMB[1|2] quota msgs.

Signed-off-by: Noel Power <noel.power at suse.com>
---
 source3/libsmb/cli_smb2_fnum.c | 83 ++++++++++++++++++++++++++++--------------
 source3/libsmb/cliquota.c      | 74 ++++++++++++++-----------------------
 source3/wscript_build          |  1 +
 3 files changed, 83 insertions(+), 75 deletions(-)

diff --git a/source3/libsmb/cli_smb2_fnum.c b/source3/libsmb/cli_smb2_fnum.c
index 7977bea..25aa32d 100644
--- a/source3/libsmb/cli_smb2_fnum.c
+++ b/source3/libsmb/cli_smb2_fnum.c
@@ -41,6 +41,7 @@
 #include "lib/util_ea.h"
 #include "librpc/gen_ndr/ndr_ioctl.h"
 #include "ntioctl.h"
+#include "librpc/gen_ndr/ndr_quota.h"
 
 struct smb2_hnd {
 	uint64_t fid_persistent;
@@ -2395,12 +2396,16 @@ NTSTATUS cli_smb2_get_user_quota(struct cli_state *cli,
 {
 	NTSTATUS status;
 	DATA_BLOB inbuf = data_blob_null;
+	DATA_BLOB info_blob = data_blob_null;
 	DATA_BLOB outbuf = data_blob_null;
 	struct smb2_hnd *ph = NULL;
 	TALLOC_CTX *frame = talloc_stackframe();
 	unsigned sid_len;
 	unsigned int offset;
-	uint8_t *buf;
+	struct smb2_query_quota_info query = {0};
+	struct file_get_quota_info info = {0};
+	enum ndr_err_code err;
+	struct ndr_push *ndr_push = NULL;
 
 	if (smbXcli_conn_has_async_calls(cli->conn)) {
 		/*
@@ -2422,27 +2427,49 @@ NTSTATUS cli_smb2_get_user_quota(struct cli_state *cli,
 
 	sid_len = ndr_size_dom_sid(&pqt->sid, 0);
 
-	inbuf = data_blob_talloc_zero(frame, 24 + sid_len);
-	if (inbuf.data == NULL) {
+	query.return_single = 1;
+	if (sid_len < 0) {
+		status = NT_STATUS_INVALID_PARAMETER;
+		goto fail;
+	}
+
+	ndr_push = ndr_push_init_ctx(frame);
+	info.next_entry_offset = 0;
+	info.sid_length = sid_len;
+	info.sid = pqt->sid;
+
+	err = ndr_push_struct_blob(
+			&info_blob,
+			frame,
+			&info,
+			(ndr_push_flags_fn_t)ndr_push_file_get_quota_info);
+
+	if (!NDR_ERR_CODE_IS_SUCCESS(err)) {
+		status = NT_STATUS_INTERNAL_ERROR;
+		goto fail;
+	}
+
+	query.sid_list_length = info_blob.length;
+	ndr_push = ndr_push_init_ctx(frame);
+	if (!ndr_push) {
 		status = NT_STATUS_NO_MEMORY;
 		goto fail;
 	}
 
-	buf = inbuf.data;
+	err = ndr_push_smb2_query_quota_info(ndr_push,
+					     NDR_SCALARS | NDR_BUFFERS,
+					     &query);
 
-	SCVAL(buf, 0, 1);	   /* ReturnSingle */
-	SCVAL(buf, 1, 0);	   /* RestartScan */
-	SSVAL(buf, 2, 0);	   /* Reserved */
-	if (8 + sid_len < 8) {
-		status = NT_STATUS_INVALID_PARAMETER;
+	if (!NDR_ERR_CODE_IS_SUCCESS(err)) {
+		status = NT_STATUS_INTERNAL_ERROR;
 		goto fail;
 	}
-	SIVAL(buf, 4, 8 + sid_len); /* SidListLength */
-	SIVAL(buf, 8, 0);	   /* StartSidLength */
-	SIVAL(buf, 12, 0);	  /* StartSidOffset */
-	SIVAL(buf, 16, 0);	  /* NextEntryOffset */
-	SIVAL(buf, 20, sid_len);    /* SidLength */
-	sid_linearize(buf + 24, sid_len, &pqt->sid);
+
+	err = ndr_push_array_uint8(ndr_push, NDR_SCALARS, info_blob.data,
+				   info_blob.length);
+
+	inbuf.data = ndr_push->data;
+	inbuf.length = ndr_push->offset;
 
 	status = smb2cli_query_info(cli->conn, cli->timeout, cli->smb2.session,
 				    cli->smb2.tcon, 4, /* in_info_type */
@@ -2487,7 +2514,8 @@ NTSTATUS cli_smb2_list_user_quota_step(struct cli_state *cli,
 	DATA_BLOB outbuf = data_blob_null;
 	struct smb2_hnd *ph = NULL;
 	TALLOC_CTX *frame = talloc_stackframe();
-	uint8_t *buf;
+	struct smb2_query_quota_info info = {0};
+	enum ndr_err_code err;
 
 	if (smbXcli_conn_has_async_calls(cli->conn)) {
 		/*
@@ -2507,20 +2535,19 @@ NTSTATUS cli_smb2_list_user_quota_step(struct cli_state *cli,
 		goto cleanup;
 	}
 
-	inbuf = data_blob_talloc_zero(frame, 16);
-	if (inbuf.data == NULL) {
-		status = NT_STATUS_NO_MEMORY;
-		goto cleanup;
-	}
 
-	buf = inbuf.data;
+	info.restart_scan = first ? 1 : 0;
+
+	err = ndr_push_struct_blob(
+			&inbuf,
+			frame,
+			&info,
+			(ndr_push_flags_fn_t)ndr_push_smb2_query_quota_info);
 
-	SCVAL(buf, 0, 0);	     /* ReturnSingle */
-	SCVAL(buf, 1, first ? 1 : 0); /* RestartScan */
-	SSVAL(buf, 2, 0);	     /* Reserved */
-	SIVAL(buf, 4, 0);	     /* SidListLength */
-	SIVAL(buf, 8, 0);	     /* StartSidLength */
-	SIVAL(buf, 12, 0);	    /* StartSidOffset */
+	if (!NDR_ERR_CODE_IS_SUCCESS(err)) {
+		status = NT_STATUS_INTERNAL_ERROR;
+		goto cleanup;
+	}
 
 	status = smb2cli_query_info(cli->conn, cli->timeout, cli->smb2.session,
 				    cli->smb2.tcon, 4, /* in_info_type */
diff --git a/source3/libsmb/cliquota.c b/source3/libsmb/cliquota.c
index e22ccdd..c0e163c 100644
--- a/source3/libsmb/cliquota.c
+++ b/source3/libsmb/cliquota.c
@@ -24,6 +24,7 @@
 #include "../libcli/security/security.h"
 #include "trans2.h"
 #include "../libcli/smb/smbXcli_base.h"
+#include "librpc/gen_ndr/ndr_quota.h"
 
 NTSTATUS cli_get_quota_handle(struct cli_state *cli, uint16_t *quota_fnum)
 {
@@ -75,61 +76,40 @@ bool parse_user_quota_record(const uint8_t *rdata,
 			     unsigned int *offset,
 			     SMB_NTQUOTA_STRUCT *pqt)
 {
-	int sid_len;
-	SMB_NTQUOTA_STRUCT qt;
-
-	ZERO_STRUCT(qt);
-
-	if (!rdata||!offset||!pqt) {
-		smb_panic("parse_quota_record: called with NULL POINTER!");
-	}
-
-	if (rdata_count < 40) {
-		return False;
-	}
-
-	/* offset to next quota record.
-	 * 4 bytes IVAL(rdata,0)
-	 * unused here...
-	 */
-	*offset = IVAL(rdata,0);
-
-	/* sid len */
-	sid_len = IVAL(rdata,4);
-	if (40 + sid_len < 40) {
-		return false;
-	}
+	struct file_quota_information info = {0};
+	TALLOC_CTX *frame = talloc_stackframe();
+	DATA_BLOB blob;
+	enum ndr_err_code err;
+	bool result;
 
-	if (rdata_count < 40+sid_len) {
-		return False;		
-	}
+	blob.data = discard_const_p(uint8_t, rdata);
+	blob.length = rdata_count;
+	err = ndr_pull_struct_blob(
+			&blob,
+			frame,
+			&info,
+			(ndr_pull_flags_fn_t)ndr_pull_file_quota_information);
 
-	if (*offset != 0 && *offset < 40 + sid_len) {
-		return false;
+	if (!NDR_ERR_CODE_IS_SUCCESS(err)) {
+		result = false;
+		goto out;
 	}
 
-	/* unknown 8 bytes in pdata 
-	 * maybe its the change time in NTTIME
-	 */
-
-	/* the used space 8 bytes (uint64_t)*/
-	qt.usedspace = BVAL(rdata,16);
-
-	/* the soft quotas 8 bytes (uint64_t)*/
-	qt.softlim = BVAL(rdata,24);
-
-	/* the hard quotas 8 bytes (uint64_t)*/
-	qt.hardlim = BVAL(rdata,32);
+	*offset = info.next_entry_offset;
 
-	if (!sid_parse(rdata+40,sid_len,&qt.sid)) {
-		return false;
-	}
+	ZERO_STRUCTP(pqt);
+	pqt->usedspace = info.quota_used;
 
-	qt.qtype = SMB_USER_QUOTA_TYPE;
+	pqt->softlim = info.quota_threshold;
 
-	*pqt = qt;
+	pqt->hardlim = info.quota_limit;
 
-	return True;
+	pqt->qtype = SMB_USER_QUOTA_TYPE;
+	pqt->sid = info.sid;
+	result = true;
+out:
+	TALLOC_FREE(frame);
+	return result;
 }
 
 NTSTATUS parse_user_quota_list(const uint8_t *curdata,
diff --git a/source3/wscript_build b/source3/wscript_build
index d3f57e4..89a10ed 100644
--- a/source3/wscript_build
+++ b/source3/wscript_build
@@ -487,6 +487,7 @@ bld.SAMBA3_LIBRARY('libsmb',
                         LIBTSOCKET
                         KRBCLIENT
                         NDR_IOCTL
+                        NDR_QUOTA
                         cli_smb_common
                         util_cmdline
                         tevent
-- 
2.10.2


From ea200074856dcba8aa9f42249770e89eaa05756e Mon Sep 17 00:00:00 2001
From: Noel Power <noel.power at suse.com>
Date: Wed, 1 Mar 2017 16:51:06 +0000
Subject: [PATCH 09/14] s3/libsmb: Modify build_user_quota_buffer with ndr
 structs and push/pull funcs.

Signed-off-by: Noel Power <noel.power at suse.com>
---
 source3/libsmb/cliquota.c | 149 ++++++++++++++++++++--------------------------
 1 file changed, 63 insertions(+), 86 deletions(-)

diff --git a/source3/libsmb/cliquota.c b/source3/libsmb/cliquota.c
index c0e163c..3159265 100644
--- a/source3/libsmb/cliquota.c
+++ b/source3/libsmb/cliquota.c
@@ -197,111 +197,80 @@ NTSTATUS build_user_quota_buffer(SMB_NTQUOTA_LIST *qt_list,
 				 DATA_BLOB *outbuf,
 				 SMB_NTQUOTA_LIST **end_ptr)
 {
-	uint32_t qt_len = 0;
-	uint8_t *entry;
-	uint32_t entry_len;
-	int sid_len;
-	SMB_NTQUOTA_LIST *qtl;
-	DATA_BLOB qbuf = data_blob_null;
-	NTSTATUS status = NT_STATUS_UNSUCCESSFUL;
-
+	int ndr_flags = NDR_SCALARS | NDR_BUFFERS;
+	struct ndr_push *header_ndr = ndr_push_init_ctx(mem_ctx);
+	uint32_t start_offset = 0;
+	uint32_t num_pushed = 0;
+	uint32_t next_align = 0;
+	bool max_data_exceeded = false;
 	if (qt_list == NULL) {
-		status = NT_STATUS_OK;
-		*outbuf = data_blob_null;
-		if (end_ptr) {
-			*end_ptr = NULL;
+		return NT_STATUS_NO_MORE_ENTRIES;
+	}
+	for (;qt_list!=NULL; qt_list=qt_list->next, num_pushed++) {
+		struct file_quota_information info;
+		enum ndr_err_code err;
+		ZERO_STRUCT(info);
+		info.sid_length = ndr_size_dom_sid(&qt_list->quotas->sid, 0);
+
+		if (maxlen && ((header_ndr->offset - next_align)
+		   + sizeof(info.next_entry_offset)
+		   + sizeof(info.sid_length)
+		   + sizeof(info.change_time)
+		   + sizeof(info.quota_used)
+		   + sizeof(info.quota_threshold)
+		   + sizeof(info.quota_limit)
+		   + info.sid_length) > maxlen) {
+			max_data_exceeded = true;
+			break;
 		}
-		return NT_STATUS_OK;
-	}
+		start_offset = header_ndr->offset;
 
-	for (qtl = qt_list; qtl != NULL; qtl = qtl->next) {
+		info.sid = qt_list->quotas->sid;
+		info.quota_used = qt_list->quotas->usedspace;
+		info.quota_threshold = qt_list->quotas->softlim;
+		info.quota_limit = qt_list->quotas->hardlim;
 
-		sid_len = ndr_size_dom_sid(&qtl->quotas->sid, 0);
-		if (47 + sid_len < 47) {
-			status = NT_STATUS_INVALID_PARAMETER;
-			goto fail;
-		}
-		entry_len = 40 + sid_len;
-		entry_len = ((entry_len + 7) / 8) * 8;
+		err = ndr_push_file_quota_information(header_ndr,
+						      ndr_flags,
+						      &info);
 
-		if (qt_len + entry_len < qt_len) {
-			status = NT_STATUS_INVALID_PARAMETER;
-			goto fail;
+		if (!NDR_ERR_CODE_IS_SUCCESS(err)) {
+			DBG_DEBUG("Failed to pull the quota sid\n");
+			return NT_STATUS_INTERNAL_ERROR;
 		}
-		qt_len += entry_len;
-	}
 
-	if (maxlen > 0 && qt_len > maxlen) {
-		qt_len = maxlen;
-	}
+		/* pidl will align to 8 bytes due to 8 byte members*/
+		next_align = header_ndr->offset;
+		ndr_push_align(header_ndr, 8);
+		next_align = header_ndr->offset - next_align;
+		info.next_entry_offset = header_ndr->offset - start_offset;
 
-	qbuf = data_blob_talloc_zero(mem_ctx, qt_len);
-	if (qbuf.data == NULL) {
-		status = NT_STATUS_NO_MEMORY;
-		goto fail;
+		/* write nextentryoffset to the buffer too */
+		SIVAL(header_ndr->data, start_offset, info.next_entry_offset);
 	}
 
-	for (qt_len = 0, entry = qbuf.data; qt_list != NULL;
-	     qt_list = qt_list->next, qt_len += entry_len, entry += entry_len) {
-
-		sid_len = ndr_size_dom_sid(&qt_list->quotas->sid, 0);
-		entry_len = 40 + sid_len;
-		entry_len = ((entry_len + 7) / 8) * 8;
-
-		if (qt_len + entry_len > qbuf.length) {
-			/* check for not-enough room even for a single
-			 * entry
-			 */
-			if (qt_len == 0) {
-				status = NT_STATUS_BUFFER_TOO_SMALL;
-				goto fail;
-			}
-
-			break;
-		}
-
-		/* nextoffset entry 4 bytes */
-		SIVAL(entry, 0, entry_len);
 
-		/* then the len of the SID 4 bytes */
-		SIVAL(entry, 4, sid_len);
+	outbuf->length =  header_ndr->offset - next_align;
+	outbuf->data = header_ndr->data;
 
-		/* NTTIME of last record change */
-		SBIG_UINT(entry, 8, (uint64_t)0);
-
-		/* the used disk space 8 bytes uint64_t */
-		SBIG_UINT(entry, 16, qt_list->quotas->usedspace);
-
-		/* the soft quotas 8 bytes uint64_t */
-		SBIG_UINT(entry, 24, qt_list->quotas->softlim);
-
-		/* the hard quotas 8 bytes uint64_t */
-		SBIG_UINT(entry, 32, qt_list->quotas->hardlim);
-
-		/* and now the SID */
-		sid_linearize((uint8_t *)(entry + 40), sid_len,
-			      &qt_list->quotas->sid);
+	if (num_pushed) {
+		/* terminate the list */
+		SIVAL(header_ndr->data, start_offset, 0);
 	}
 
-	/* overwrite the offset of the last entry */
-	SIVAL(entry - entry_len, 0, 0);
-
-	/*potentially shrink the buffer if max was given
-	 * and we haven't quite reached the max
+	/*
+	 * if we failed to write a single file_quota_information
+	 * issue BUFFER_TOO_SMALL
 	 */
-	qbuf.length = qt_len;
-	*outbuf = qbuf;
-	qbuf = data_blob_null;
-	status = NT_STATUS_OK;
+	if (max_data_exceeded && num_pushed < 1) {
+		return NT_STATUS_BUFFER_TOO_SMALL;
+	}
 
 	if (end_ptr) {
 		*end_ptr = qt_list;
 	}
 
-fail:
-	data_blob_free(&qbuf);
-
-	return status;
+	return NT_STATUS_OK;
 }
 
 NTSTATUS build_fs_quota_buffer(TALLOC_CTX *mem_ctx,
@@ -422,7 +391,15 @@ cli_set_user_quota(struct cli_state *cli, int quota_fnum, SMB_NTQUOTA_LIST *qtl)
 
 	status = build_user_quota_buffer(qtl, 0, talloc_tos(), &data, NULL);
 	if (!NT_STATUS_IS_OK(status)) {
-		goto cleanup;
+		/*
+		 * smb1 doesn't send NT_STATUS_NO_MORE_ENTRIES so swallow
+		 * this status.
+		 */
+		if (NT_STATUS_EQUAL(status, NT_STATUS_NO_MORE_ENTRIES)) {
+			status = NT_STATUS_OK;
+		} else {
+			goto cleanup;
+		}
 	}
 
 	SSVAL(setup + 0, 0, NT_TRANSACT_SET_USER_QUOTA);
-- 
2.10.2


From b45cd6407749c88b7a95e7065f312d0d75265cbe Mon Sep 17 00:00:00 2001
From: Noel Power <noel.power at suse.com>
Date: Wed, 8 Mar 2017 14:27:27 +0000
Subject: [PATCH 10/14] s3/script/test: modify existing smbcquota test to use
 SMB2 in addition to SMB1.

Signed-off-by: Noel Power <noel.power at suse.com>
---
 source3/script/tests/test_dfree_quota.sh | 14 ++++++++++++--
 1 file changed, 12 insertions(+), 2 deletions(-)

diff --git a/source3/script/tests/test_dfree_quota.sh b/source3/script/tests/test_dfree_quota.sh
index 6e227c4..43f3623 100755
--- a/source3/script/tests/test_dfree_quota.sh
+++ b/source3/script/tests/test_dfree_quota.sh
@@ -135,13 +135,21 @@ test_smbcquotas() {
     conf="$2"
     user="$3"
     expected="$4"
+    proto="$5"
 	shift
     shift
     shift
     shift
+    shift
 	subunit_start_test "$name"
     setup_conf "$conf" "."
-	output=$($VALGRIND $smbcquotas //$SERVER/dfq $@ 2>/dev/null | tr '\\' '/')
+    if [ "$proto"  = "smb2" ]; then
+        mproto="-m SMB2"
+    else
+        mproto="-m SMB1"
+    fi
+
+	output=$($VALGRIND $smbcquotas $mproto //$SERVER/dfq $@ 2>/dev/null | tr '\\' '/')
 	status=$?
 	if [ "$status" = "0" ]; then
 		received=$(echo "$output" | awk "/$SERVER\\/$user/ {printf \"%s%s%s\", \$3, \$4, \$5}")
@@ -162,7 +170,9 @@ test_smbclient_dfree "Test dfree subdir SMB3 no quota" dfq "subdir1" "conf1 . co
 test_smbclient_dfree "Test dfree subdir NT1 no quota" dfq "subdir1" "conf1 . conf2 subdir1" "10 1024. 5" -U$USERNAME%$PASSWORD --option=clientmaxprotocol=NT1 || failed=`expr $failed + 1`
 test_smbclient_dfree "Test large disk" dfq "." "conf3 ." "1125899906842624 1024. 3000" -U$USERNAME%$PASSWORD --option=clientmaxprotocol=SMB3 || failed=`expr $failed + 1`
 #basic quota test (SMB1 only)
-test_smbcquotas "Test user quota" confq1 $USERNAME "40960/4096000/3072000" -U$USERNAME%$PASSWORD --option=clientmaxprotocol=NT1 || failed=`expr $failed + 1`
+test_smbcquotas "Test user quota" confq1 $USERNAME "40960/4096000/3072000" "smb1" -U$USERNAME%$PASSWORD --option=clientmaxprotocol=NT1 || failed=`expr $failed + 1`
+#basic quota test (SMB2 only)
+test_smbcquotas "Test user quota" confq1 $USERNAME "40960/4096000/3072000" "smb2" -U$USERNAME%$PASSWORD --option=clientmaxprotocol=SMB2 || failed=`expr $failed + 1`
 
 #quota limit > disk size, remaining quota > disk free
 test_smbclient_dfree "Test dfree share root df vs quota case 1" dfq "." "confdfq1 ." "80 1024. 40" -U$USERNAME%$PASSWORD --option=clientmaxprotocol=SMB3 || failed=`expr $failed + 1`
-- 
2.10.2


From 5f3fb52275394fbf74bc8e0c91d7fa2b854eb412 Mon Sep 17 00:00:00 2001
From: Noel Power <noel.power at suse.com>
Date: Wed, 8 Mar 2017 17:22:40 +0000
Subject: [PATCH 11/14] s3/libsmb: new fill_quota_buffer common function for
 client/server use.

Signed-off-by: Noel Power <noel.power at suse.com>
---
 source3/libsmb/cli_smb2_fnum.c |  87 ++++++++++++++++++++++++++++++
 source3/libsmb/cli_smb2_fnum.h |   7 +++
 source3/libsmb/cliquota.c      |  80 +++------------------------
 source3/smbd/nttrans.c         | 119 +++++------------------------------------
 4 files changed, 112 insertions(+), 181 deletions(-)

diff --git a/source3/libsmb/cli_smb2_fnum.c b/source3/libsmb/cli_smb2_fnum.c
index 25aa32d..80a2bdd 100644
--- a/source3/libsmb/cli_smb2_fnum.c
+++ b/source3/libsmb/cli_smb2_fnum.c
@@ -3674,3 +3674,90 @@ NTSTATUS cli_smb2_ftruncate(struct cli_state *cli,
 	TALLOC_FREE(frame);
 	return status;
 }
+
+ NTSTATUS fill_quota_buffer(TALLOC_CTX *mem_ctx,
+			      SMB_NTQUOTA_LIST *tmp_list,
+			      bool return_single,
+			      uint32_t max_data,
+			      DATA_BLOB *blob,
+			      SMB_NTQUOTA_LIST **end_ptr)
+{
+	int ndr_flags = NDR_SCALARS | NDR_BUFFERS;
+	struct ndr_push *header_ndr = ndr_push_init_ctx(mem_ctx);
+	uint32_t start_offset = 0;
+	uint32_t num_pushed = 0;
+	uint32_t next_align = 0;
+	bool max_data_exceeded = false;
+
+	if (tmp_list == NULL) {
+		return NT_STATUS_NO_MORE_ENTRIES;
+	}
+	for (;tmp_list!=NULL; tmp_list=tmp_list->next, num_pushed++) {
+		struct file_quota_information info;
+		enum ndr_err_code err;
+		ZERO_STRUCT(info);
+		info.sid_length = ndr_size_dom_sid(&tmp_list->quotas->sid, 0);
+
+		if (max_data && ((header_ndr->offset - next_align)
+		   + sizeof(info.next_entry_offset)
+		   + sizeof(info.sid_length)
+		   + sizeof(info.change_time)
+		   + sizeof(info.quota_used)
+		   + sizeof(info.quota_threshold)
+		   + sizeof(info.quota_limit)
+		   + info.sid_length) > max_data) {
+			max_data_exceeded = true;
+			break;
+		}
+		start_offset = header_ndr->offset;
+
+		info.sid = tmp_list->quotas->sid;
+		info.quota_used = tmp_list->quotas->usedspace;
+		info.quota_threshold = tmp_list->quotas->softlim;
+		info.quota_limit = tmp_list->quotas->hardlim;
+
+		err = ndr_push_file_quota_information(header_ndr,
+						      ndr_flags,
+						      &info);
+
+		if (!NDR_ERR_CODE_IS_SUCCESS(err)) {
+			DBG_DEBUG("Failed to pull the quota sid\n");
+			return NT_STATUS_INTERNAL_ERROR;
+		}
+
+		/* pidl will align to 8 bytes due to 8 byte members*/
+		next_align = header_ndr->offset;
+		ndr_push_align(header_ndr, 8);
+		next_align = header_ndr->offset - next_align;
+		info.next_entry_offset = header_ndr->offset - start_offset;
+
+		if (return_single) {
+			break;
+		}
+
+		/* write nextentryoffset to the buffer too */
+		SIVAL(header_ndr->data, start_offset, info.next_entry_offset);
+	}
+
+	if (end_ptr) {
+		*end_ptr = tmp_list;
+	}
+
+	blob->length = header_ndr->offset - next_align;
+	blob->data = header_ndr->data;
+
+	if (num_pushed) {
+		/* terminate the list */
+		SIVAL(header_ndr->data, start_offset, 0);
+	}
+
+	/*
+	 * if we failed to write a single file_quota_information
+	 * issue BUFFER_TOO_SMALL
+	 */
+	if (max_data_exceeded && num_pushed < 1) {
+		return NT_STATUS_BUFFER_TOO_SMALL;
+	}
+
+	return NT_STATUS_OK;
+}
diff --git a/source3/libsmb/cli_smb2_fnum.h b/source3/libsmb/cli_smb2_fnum.h
index 12c42a2..e5a3ca1 100644
--- a/source3/libsmb/cli_smb2_fnum.h
+++ b/source3/libsmb/cli_smb2_fnum.h
@@ -211,4 +211,11 @@ NTSTATUS cli_smb2_shadow_copy_data(TALLOC_CTX *mem_ctx,
 NTSTATUS cli_smb2_ftruncate(struct cli_state *cli,
 			uint16_t fnum,
 			uint64_t newsize);
+
+NTSTATUS fill_quota_buffer(TALLOC_CTX *mem_ctx,
+			      SMB_NTQUOTA_LIST *tmp_list,
+			      bool return_single,
+			      uint32_t max_data,
+			      DATA_BLOB *blob,
+			      SMB_NTQUOTA_LIST **end_ptr);
 #endif /* __SMB2CLI_FNUM_H__ */
diff --git a/source3/libsmb/cliquota.c b/source3/libsmb/cliquota.c
index 3159265..fca16d6 100644
--- a/source3/libsmb/cliquota.c
+++ b/source3/libsmb/cliquota.c
@@ -197,80 +197,12 @@ NTSTATUS build_user_quota_buffer(SMB_NTQUOTA_LIST *qt_list,
 				 DATA_BLOB *outbuf,
 				 SMB_NTQUOTA_LIST **end_ptr)
 {
-	int ndr_flags = NDR_SCALARS | NDR_BUFFERS;
-	struct ndr_push *header_ndr = ndr_push_init_ctx(mem_ctx);
-	uint32_t start_offset = 0;
-	uint32_t num_pushed = 0;
-	uint32_t next_align = 0;
-	bool max_data_exceeded = false;
-	if (qt_list == NULL) {
-		return NT_STATUS_NO_MORE_ENTRIES;
-	}
-	for (;qt_list!=NULL; qt_list=qt_list->next, num_pushed++) {
-		struct file_quota_information info;
-		enum ndr_err_code err;
-		ZERO_STRUCT(info);
-		info.sid_length = ndr_size_dom_sid(&qt_list->quotas->sid, 0);
-
-		if (maxlen && ((header_ndr->offset - next_align)
-		   + sizeof(info.next_entry_offset)
-		   + sizeof(info.sid_length)
-		   + sizeof(info.change_time)
-		   + sizeof(info.quota_used)
-		   + sizeof(info.quota_threshold)
-		   + sizeof(info.quota_limit)
-		   + info.sid_length) > maxlen) {
-			max_data_exceeded = true;
-			break;
-		}
-		start_offset = header_ndr->offset;
-
-		info.sid = qt_list->quotas->sid;
-		info.quota_used = qt_list->quotas->usedspace;
-		info.quota_threshold = qt_list->quotas->softlim;
-		info.quota_limit = qt_list->quotas->hardlim;
-
-		err = ndr_push_file_quota_information(header_ndr,
-						      ndr_flags,
-						      &info);
-
-		if (!NDR_ERR_CODE_IS_SUCCESS(err)) {
-			DBG_DEBUG("Failed to pull the quota sid\n");
-			return NT_STATUS_INTERNAL_ERROR;
-		}
-
-		/* pidl will align to 8 bytes due to 8 byte members*/
-		next_align = header_ndr->offset;
-		ndr_push_align(header_ndr, 8);
-		next_align = header_ndr->offset - next_align;
-		info.next_entry_offset = header_ndr->offset - start_offset;
-
-		/* write nextentryoffset to the buffer too */
-		SIVAL(header_ndr->data, start_offset, info.next_entry_offset);
-	}
-
-
-	outbuf->length =  header_ndr->offset - next_align;
-	outbuf->data = header_ndr->data;
-
-	if (num_pushed) {
-		/* terminate the list */
-		SIVAL(header_ndr->data, start_offset, 0);
-	}
-
-	/*
-	 * if we failed to write a single file_quota_information
-	 * issue BUFFER_TOO_SMALL
-	 */
-	if (max_data_exceeded && num_pushed < 1) {
-		return NT_STATUS_BUFFER_TOO_SMALL;
-	}
-
-	if (end_ptr) {
-		*end_ptr = qt_list;
-	}
-
-	return NT_STATUS_OK;
+	return fill_quota_buffer(mem_ctx,
+				 qt_list,
+				 false,
+				 maxlen,
+				 outbuf,
+				 end_ptr);
 }
 
 NTSTATUS build_fs_quota_buffer(TALLOC_CTX *mem_ctx,
diff --git a/source3/smbd/nttrans.c b/source3/smbd/nttrans.c
index a341559..e622578 100644
--- a/source3/smbd/nttrans.c
+++ b/source3/smbd/nttrans.c
@@ -2460,105 +2460,6 @@ done:
 	return err;
 }
 
-static NTSTATUS fill_quota_buffer(TALLOC_CTX *mem_ctx,
-			      SMB_NTQUOTA_HANDLE *qt_handle,
-			      bool rescan,
-			      bool return_single,
-			      uint32_t max_data,
-			      uint8_t **data,
-			      uint32_t *datalen)
-{
-	SMB_NTQUOTA_STRUCT qt;
-	SMB_NTQUOTA_LIST *tmp_list;
-	int ndr_flags = NDR_SCALARS | NDR_BUFFERS;
-	struct ndr_push *header_ndr = ndr_push_init_ctx(mem_ctx);
-	uint32_t start_offset = 0;
-	uint32_t num_pushed = 0;
-	uint32_t next_align = 0;
-	bool max_data_exceeded = false;
-
-	ZERO_STRUCT(qt);
-	*data = NULL;
-	*datalen = 0;
-
-	if (rescan) {
-		tmp_list = qt_handle->quota_list;
-	} else {
-		tmp_list = qt_handle->tmp_list;
-	}
-
-	if (tmp_list == NULL) {
-		return NT_STATUS_NO_MORE_ENTRIES;
-	}
-	for (;tmp_list!=NULL; tmp_list=tmp_list->next, num_pushed++) {
-		struct file_quota_information info;
-		enum ndr_err_code err;
-		ZERO_STRUCT(info);
-		info.sid_length = ndr_size_dom_sid(&tmp_list->quotas->sid, 0);
-
-		if (((header_ndr->offset - next_align)
-		   + sizeof(info.next_entry_offset)
-		   + sizeof(info.sid_length)
-		   + sizeof(info.change_time)
-		   + sizeof(info.quota_used)
-		   + sizeof(info.quota_threshold)
-		   + sizeof(info.quota_limit)
-		   + info.sid_length) > max_data) {
-			max_data_exceeded = true;
-			break;
-		}
-		start_offset = header_ndr->offset;
-
-		info.sid = tmp_list->quotas->sid;
-		info.quota_used = tmp_list->quotas->usedspace;
-		info.quota_threshold = tmp_list->quotas->softlim;
-		info.quota_limit = tmp_list->quotas->hardlim;
-
-		err = ndr_push_file_quota_information(header_ndr,
-						      ndr_flags,
-						      &info);
-
-		if (!NDR_ERR_CODE_IS_SUCCESS(err)) {
-			DBG_DEBUG("Failed to pull the quota sid\n");
-			return NT_STATUS_INTERNAL_ERROR;
-		}
-
-		/* pidl will align to 8 bytes due to 8 byte members*/
-		next_align = header_ndr->offset;
-		ndr_push_align(header_ndr, 8);
-		next_align = header_ndr->offset - next_align;
-		info.next_entry_offset = header_ndr->offset - start_offset;
-
-		if (return_single) {
-			break;
-		}
-
-		/* write nextentryoffset to the buffer too */
-		SIVAL(header_ndr->data, start_offset, info.next_entry_offset);
-	}
-
-
-	qt_handle->tmp_list = tmp_list;
-
-	*datalen = header_ndr->offset - next_align;
-	*data = header_ndr->data;
-
-	if (num_pushed) {
-		/* terminate the list */
-		SIVAL(header_ndr->data, start_offset, 0);
-	}
-
-	/*
-	 * if we failed to write a single file_quota_information
-	 * issue BUFFER_TOO_SMALL
-	 */
-	if (max_data_exceeded && num_pushed < 1) {
-		return NT_STATUS_BUFFER_TOO_SMALL;
-	}
-
-	return NT_STATUS_OK;
-}
-
 NTSTATUS smbd_do_query_getinfo_quota(TALLOC_CTX *mem_ctx,
 				     files_struct *fsp,
 				     struct smb2_query_quota_info *info,
@@ -2569,8 +2470,8 @@ NTSTATUS smbd_do_query_getinfo_quota(TALLOC_CTX *mem_ctx,
 {
 	NTSTATUS status;
 	SMB_NTQUOTA_HANDLE *qt_handle = NULL;
-	uint8_t *data = NULL;
-	uint32_t data_size = 0;
+	SMB_NTQUOTA_LIST *qt_list = NULL;
+	DATA_BLOB blob = data_blob_null;
 	enum ndr_err_code err;
 
 	DBG_DEBUG("sidlistlength = %d, startsidlength = %d, "
@@ -2655,14 +2556,18 @@ NTSTATUS smbd_do_query_getinfo_quota(TALLOC_CTX *mem_ctx,
 		}
 	}
 
-	status = fill_quota_buffer(mem_ctx, qt_handle,
-				   info->restart_scan !=0,
+	if (info->restart_scan !=0 ) {
+		qt_list = qt_handle->quota_list;
+	} else {
+		qt_list = qt_handle->tmp_list;
+	}
+	status = fill_quota_buffer(mem_ctx, qt_list,
 				   info->return_single != 0,
 				   max_data_count,
-				   &data,
-				   &data_size);
-	*p_data = data;
-	*p_data_size = data_size;
+				   &blob,
+				   &qt_handle->tmp_list);
+	*p_data = blob.data;
+	*p_data_size = blob.length;
 done:
 	return status;
 }
-- 
2.10.2


From 076e71c662a17ae66dd10e2bd981d4c993a5f177 Mon Sep 17 00:00:00 2001
From: Noel Power <noel.power at suse.com>
Date: Wed, 22 Mar 2017 14:53:22 +0000
Subject: [PATCH 12/14] s3/lib: Fix misleading typo in debug message

Signed-off-by: Noel Power <noel.power at suse.com>
---
 source3/lib/sysquotas.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/source3/lib/sysquotas.c b/source3/lib/sysquotas.c
index eef87be..2d829e4 100644
--- a/source3/lib/sysquotas.c
+++ b/source3/lib/sysquotas.c
@@ -418,7 +418,7 @@ static int command_set_quota(const char *path, enum SMB_QUOTA_TYPE qtype, unid_t
 			return -1;
 		}
 
-		DEBUG (3, ("get_quota: Running command %s\n", syscmd));
+		DEBUG (3, ("set_quota: Running command %s\n", syscmd));
 
 		lines = file_lines_pload(talloc_tos(), syscmd, NULL);
 		SAFE_FREE(syscmd);
-- 
2.10.2


From cb77c4f2b89a28a5930c7dd020c42c01dae815c4 Mon Sep 17 00:00:00 2001
From: Noel Power <noel.power at suse.com>
Date: Tue, 21 Mar 2017 08:29:59 +0000
Subject: [PATCH 13/14] s3/script/tests: Add simple (smb1 & smb2) get/set/list
 tests for smbcquotas

Signed-off-by: Noel Power <noel.power at suse.com>
---
 selftest/target/Samba3.pm              |  10 ++
 source3/script/tests/getset_quota.py   | 154 +++++++++++++++++++++
 source3/script/tests/test_smbcquota.py | 244 +++++++++++++++++++++++++++++++++
 source3/script/tests/test_smbcquota.sh |  46 +++++++
 source3/selftest/tests.py              |   1 +
 5 files changed, 455 insertions(+)
 create mode 100755 source3/script/tests/getset_quota.py
 create mode 100755 source3/script/tests/test_smbcquota.py
 create mode 100755 source3/script/tests/test_smbcquota.sh

diff --git a/selftest/target/Samba3.pm b/selftest/target/Samba3.pm
index d754b5f..df0d680 100755
--- a/selftest/target/Samba3.pm
+++ b/selftest/target/Samba3.pm
@@ -647,6 +647,9 @@ sub setup_fileserver($$)
 	push(@dirs, "$dfree_share_dir/subdir2");
 	push(@dirs, "$dfree_share_dir/subdir3");
 
+	my $quotadir_dir="$share_dir/quota";
+	push(@dirs, $quotadir_dir);
+
 	my $valid_users_sharedir="$share_dir/valid_users";
 	push(@dirs,$valid_users_sharedir);
 
@@ -660,6 +663,8 @@ sub setup_fileserver($$)
 	push(@dirs,$smbget_sharedir);
 
 	my $fileserver_options = "
+	get quota command = $prefix_abs/getset_quota.py
+	set quota command = $prefix_abs/getset_quota.py
 [lowercase]
 	path = $lower_case_share_dir
 	comment = smb username is [%U]
@@ -1843,6 +1848,11 @@ sub provision($$$$$$$$)
 	vfs objects = acl_xattr fake_acls xattr_tdb fake_dfq
 	admin users = $unix_name
 	include = $dfqconffile
+
+[quotadir]
+	path = $shrdir/quota
+	admin users = $unix_name
+
 [dfq_owner]
 	path = $shrdir/dfree
 	vfs objects = acl_xattr fake_acls xattr_tdb fake_dfq
diff --git a/source3/script/tests/getset_quota.py b/source3/script/tests/getset_quota.py
new file mode 100755
index 0000000..0254aa5
--- /dev/null
+++ b/source3/script/tests/getset_quota.py
@@ -0,0 +1,154 @@
+#!/usr/bin/env python3
+# Unix SMB/CIFS implementation.
+# Tests for smbcquotas
+# Copyright (C) Noel Power 2017
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+import sys
+import traceback
+import logging
+import os
+
+USER_QUOTAS = 1
+USER_DEFAULT_QUOTAS = 2
+GROUP_QUOTAS = 3
+GROUP_DEFAULT_QUOTAS = 4
+
+#Quota model
+
+class Quota:
+	def __init__(self):
+		self.flags = 0
+		self.quotatype = USER_DEFAULT_QUOTAS
+		self.uid = 0
+		self.usedblocks = 0
+		self.softlimit = 0
+		self.hardlimit = 0
+		self.hardlimit = 0
+		self.usedinodes = 0
+		self.slimitinodes = 0
+		self.hlimitinodes = 0
+
+def quota_to_str(item):
+	result = str(item.flags) + " " + str(item.usedblocks) + " " + str(item.softlimit) + " " + str(item.hardlimit) + " " + str(item.usedinodes) + " " + str(item.slimitinodes) + " " + str(item.hlimitinodes)
+	return result
+
+def quota_to_db_str(item):
+	result = item.uid + " " + str(item.usedblocks) + " " + str(item.softlimit) + " " + str(item.hardlimit) + " " + str(item.usedinodes) + " " + str(item.slimitinodes) + " " + str(item.hlimitinodes)
+	return result
+
+def load_quotas(input_file):
+	fileContents = open(input_file,"r")
+	lineno = 0
+	quotas = []
+	for line in fileContents:
+		if line.strip().startswith("#"):
+			continue
+		content = line.strip().split()
+		quota = Quota()
+		if len(content) < 7:
+			logging.debug("ignoring line %d, doesn't have enough fields\n"%lineno)
+		else:
+			quota.flags = 2
+			quota.uid = content[0]
+			quota.usedblocks = content[1]
+			quota.softlimit = content[2]
+			quota.hardlimit = content[3]
+			quota.usedinodes = content[4]
+			quota.slimitinodes = content[5]
+			quota.hlimitinodes = content[6]
+			quotas.append(quota)
+
+	fileContents.close()
+	return quotas
+
+def set_quotas(quota_list, output_file):
+	filecontents = open(output_file,"w+")
+	if filecontents == None:
+		return False;
+	lines = ""
+	for quota in quota_list:
+		next_line = quota_to_db_str(quota)
+		if next_line:
+			lines = lines + next_line + "\n"
+	filecontents.write(lines)
+	filecontents.close()
+	return True
+
+def get_quotas(uid, quota_list):
+	logging.debug("in get_quotas\n")
+	for quota in quota_list:
+		if quota.uid == uid:
+			return quota
+	return None
+
+def main():
+	logging.basicConfig(format='%(asctime)s %(message)s', level=logging.DEBUG)
+	logging.debug("system args passed are %s\n"% str(sys.argv))
+	quota_file_dir = os.path.dirname(sys.argv[0]);
+	quota_file_db = os.path.join(quota_file_dir,"quotas.db")
+	logging.debug("quota db is located %s\n", quota_file_db)
+	quota_list = load_quotas(quota_file_db)
+	logging.debug("quotas loaded have %s entries\n", len(quota_list))
+	result = None
+	if len(sys.argv) == 4:
+		# Get Quota
+		directory = sys.argv[1]
+		if sys.argv[2] == "1":
+			query_type = USER_QUOTAS
+		elif sys.argv[2] == "2":
+			query_type = USER_DEFAULT_QUOTAS
+		elif sys.argv[2] == "3":
+			query_type = GROUP_QUOTAS
+		elif sys.argv[2] == "4":
+			query_type = GROUP_DEFAULT_QUOTAS
+		uid = sys.argv[3]
+		quota = get_quotas(uid, quota_list)
+		if quota is None:
+			logging.debug("no result for uid %s"%uid)
+		else:
+			result = quota_to_str(quota)
+			logging.debug("got result for uid %s\n"%uid);
+		if result is None:
+			result = "0 0 0 0 0 0 0"
+		logging.debug("for uid %s returning quotas %s\n"%(uid,result))
+		print("%s"%result)
+	elif len(sys.argv) > 8:
+		# Set Quota
+		quota = Quota()
+		directory = sys.argv[1]
+		quota.query_type = sys.argv[2]
+		quota.uid = sys.argv[3]
+		quota.flags = sys.argv[4]
+		quota.softlimit = sys.argv[5]
+		quota.hardlimit = sys.argv[6]
+		quota.slimitinodes = sys.argv[7]
+		quota.hlimitinodes = sys.argv[8]
+		found = get_quotas(quota.uid, quota_list)
+		if found:
+			found.query_type = quota.query_type
+			found.uid = quota.uid
+			found.flags = quota.flags
+			found.softlimit = quota.softlimit
+			found.hardlimit = quota.hardlimit
+			found.slimitinodes = quota.slimitinodes
+			found.hlimitinodes = quota.hlimitinodes
+		else:
+			quota_list.append(quota)
+		if set_quotas(quota_list,quota_file_db):
+			print ("%s\n"%quota_to_str(quota_list[-1]))
+	return
+if __name__ == '__main__':
+    main()
diff --git a/source3/script/tests/test_smbcquota.py b/source3/script/tests/test_smbcquota.py
new file mode 100755
index 0000000..52061f2
--- /dev/null
+++ b/source3/script/tests/test_smbcquota.py
@@ -0,0 +1,244 @@
+#!/usr/bin/env python3
+# Unix SMB/CIFS implementation.
+# Tests for smbcquotas
+# Copyright (C) Noel Power 2017
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+import os, subprocess, sys
+import traceback
+import logging
+import shutil
+
+USER_QUOTAS = 1
+USER_DEFAULT_QUOTAS = 2
+GROUP_QUOTAS = 3
+GROUP_DEFAULT_QUOTAS = 4
+BLOCK_SIZE = 1024
+DEFAULT_SOFTLIM = 2
+DEFAULT_HARDLIM = 4
+
+class test_env:
+	def __init__(self):
+		self.server = None
+		self.domain = None
+		self.username = None
+		self.password = None
+		self.envdir = None
+		self.quota_db = None
+		self.smbcquotas = None
+		self.users = []
+
+class user_info:
+	def __init__(self):
+		self.uid = 0
+		self.username = ""
+		self.softlim = 0
+		self.hardlim = 0
+
+class Quota:
+	def __init__(self):
+		self.flags = 0
+		self.quotatype = USER_DEFAULT_QUOTAS
+		self.uid = 0
+		self.usedblocks = 0
+		self.softlimit = 0
+		self.hardlimit = 0
+		self.hardlimit = 0
+		self.usedinodes = 0
+		self.slimitinodes = 0
+		self.hlimitinodes = 0
+
+def init_quota_db(users, output_file):
+	filecontents = open(output_file,"w+")
+	lines = ""
+	default_values = "0 " + str(DEFAULT_SOFTLIM) + " " + str(DEFAULT_HARDLIM) + " 0 0 0"
+	for user in users:
+		lines = lines + user.uid + " " + default_values + "\n"
+	filecontents.write(lines)
+	filecontents.close()
+
+def load_quotas(input_file):
+	fileContents = open(input_file,"r")
+	lineno = 0
+	quotas = []
+	for line in fileContents:
+		if line.strip().startswith("#"):
+			continue
+		content = line.strip().split()
+		quota = Quota()
+		if len(content) < 7:
+			logging.debug("ignoring line %d, doesn't have enough fields\n"%lineno)
+		else:
+			quota.flags = 2
+			quota.uid = content[0]
+			quota.usedblocks = content[1]
+			quota.softlimit = content[2]
+			quota.hardlimit = content[3]
+			quota.usedinodes = content[4]
+			quota.slimitinodes = content[5]
+			quota.hlimitinodes = content[6]
+			quotas.append(quota)
+
+	fileContents.close()
+	return quotas
+
+def get_quotas(uid, quota_list):
+	for quota in quota_list:
+		if quota.uid == uid:
+			return quota
+	return None
+
+def get_users():
+	output = subprocess.Popen(['getent', 'passwd'],
+                                 stdout=subprocess.PIPE).communicate()[0].decode("utf-8").split('\n')
+	users = []
+	for line in output:
+		info = line.split(':')
+		if len(info) > 3 and info[0]:
+			user = user_info()
+			user.username = info[0]
+			user.uid = info[2]
+			logging.debug("Adding user ->%s<-\n"%user.username)
+			users.append(user)
+	return users
+
+
+
+def smbcquota_output_to_userinfo(output):
+	infos = []
+	for line in output:
+		if len(line) > 1:
+			username = line.strip(':').split()[0]
+			quota_info = line.split(':')[1].split('/')
+			if len(quota_info) > 2:
+				info = user_info()
+				info.username = username.strip()
+				info.softlim = int(quota_info[1].strip()) / BLOCK_SIZE
+				info.hardlim = int(quota_info[2].strip()) / BLOCK_SIZE
+				infos.append(info)
+	return infos
+
+def check_quota_limits(infos, softlim, hardlim):
+	if len(infos) < 1:
+		logging.debug("no users info to check :-(\n")
+		return False
+	for info in infos:
+		if int(info.softlim) != softlim:
+			logging.debug("expected softlimit %s got ->%s<-\n"%(softlim, info.softlim))
+			return False
+		if int(info.hardlim) != hardlim:
+			logging.debug("expected hardlimit limit %s got %s\n"%(hardlim,info.hardlim))
+			return False
+	return True
+
+class test_base:
+	def __init__(self, env):
+		self.env = env
+	def run(self, protocol):
+		pass
+
+class listtest(test_base):
+	def run(self, protocol):
+		init_quota_db(self.env.users, self.env.quota_db)
+		quotas = load_quotas(self.env.quota_db)
+		args = [self.env.smbcquotas];
+		remaining_args = ['-U' + self.env.username + "%" + self.env.password, '-L', '//' + self.env.server + '/quotadir']
+		if protocol == 'smb2':
+			args.append('-m smb2')
+		args.extend(remaining_args)
+		output = subprocess.Popen([self.env.smbcquotas, '-U' + self.env.username + "%" + self.env.password, '-L', '//' + self.env.server + '/quotadir'], stdout=subprocess.PIPE).communicate()[0].decode("utf-8").split('\n')
+		infos = smbcquota_output_to_userinfo(output)
+		return check_quota_limits(infos, DEFAULT_SOFTLIM, DEFAULT_HARDLIM)
+def get_uid(name, users):
+	for user in users:
+		if user.username == name:
+			return user.uid
+	return None
+
+class gettest(test_base):
+	def run(self, protocol):
+		init_quota_db(self.env.users, self.env.quota_db)
+		quotas = load_quotas(self.env.quota_db)
+		uid = get_uid(self.env.username, self.env.users)
+		output = subprocess.Popen([self.env.smbcquotas, '-U' + self.env.username + "%" + self.env.password, '-u' + self.env.username, '//' + self.env.server + '/quotadir'], stdout=subprocess.PIPE).communicate()[0].decode("utf-8").split('\n')
+		user_infos = smbcquota_output_to_userinfo(output)
+		db_user_info = get_quotas(uid, quotas)
+		# double check, we compare the results from the db file
+		# the quota script the server uses compared to what
+		# smbcquota is telling us
+		return check_quota_limits(user_infos, int(db_user_info.softlimit), int(db_user_info.hardlimit))
+
+class settest(test_base):
+	def run(self, protocol):
+		init_quota_db(self.env.users, self.env.quota_db)
+		quotas = load_quotas(self.env.quota_db)
+		uid = get_uid(self.env.username, self.env.users)
+		old_db_user_info = get_quotas(uid, quotas)
+
+		#increase limits by 2 blocks
+		new_soft_limit = (int(old_db_user_info.softlimit) + 2) * BLOCK_SIZE
+		new_hard_limit = (int(old_db_user_info.hardlimit) + 2) * BLOCK_SIZE
+
+		new_limits = "UQLIM:%s:%d/%d"%(self.env.username, new_soft_limit, new_hard_limit)
+		logging.debug("setting new limits %s"%new_limits)
+
+		output = subprocess.Popen([self.env.smbcquotas, '-U' + self.env.username + "%" + self.env.password, '//' + self.env.server + '/quotadir', '-S', new_limits], stdout=subprocess.PIPE).communicate()[0].decode("utf-8").split('\n')
+		logging.debug("output from smbcquota is %s"%output)
+		user_infos = smbcquota_output_to_userinfo(output)
+		return check_quota_limits(user_infos, new_soft_limit / BLOCK_SIZE, new_hard_limit / BLOCK_SIZE)
+
+# map of tests
+subtest_descriptions = {
+	"list test" : listtest,
+	"get test" : gettest,
+	"set test" : settest
+}
+
+def main():
+	logging.basicConfig(format='%(asctime)s %(message)s', level=logging.DEBUG)
+
+	logging.debug("got args %s\n"%str(sys.argv))
+
+	if len(sys.argv) < 7:
+		logging.debug ("Usage: test_smbcquota.py server domain username password envdir smbcquotas\n")
+		sys.exit(1)
+	env = test_env()
+	env.server = sys.argv[1]
+	env.domain = sys.argv[2]
+	env.username = sys.argv[3]
+	env.password = sys.argv[4]
+	env.envdir = sys.argv[5]
+	env.smbcquotas = sys.argv[6]
+	quota_script = os.path.join(os.path.dirname(sys.argv[0]),
+				"getset_quota.py")
+	#copy the quota script to the evironment
+	shutil.copy2(quota_script, env.envdir)
+
+	env.quota_db = os.path.join(env.envdir, "quotas.db")
+	env.users = get_users()
+	for protocol in ['smb1', 'smb2']:
+		for key in subtest_descriptions.keys():
+			test = subtest_descriptions[key](env)
+			logging.debug("running subtest '%s' using protocol '%s'\n"%(key,protocol))
+			result = test.run(protocol)
+			if result == False:
+				logging.debug("subtest '%s' for '%s' failed\n"%(key,protocol))
+				sys.exit(1)
+			else:
+				logging.debug("subtest '%s' for '%s' passed\n"%(key,protocol))
+	sys.exit(0)
+
+if __name__ == '__main__':
+    main()
diff --git a/source3/script/tests/test_smbcquota.sh b/source3/script/tests/test_smbcquota.sh
new file mode 100755
index 0000000..be2d0f5
--- /dev/null
+++ b/source3/script/tests/test_smbcquota.sh
@@ -0,0 +1,46 @@
+#!/bin/sh
+# Unix SMB/CIFS implementation.
+# Tests for smbcquotas
+# Copyright (C) Noel Power 2017
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>. 
+#
+
+#
+# Blackbox test wrapper for smbcquota
+#
+if [ $# -lt 6 ]; then
+cat <<EOF
+Usage: test_smbcquota.sh SERVER DOMAIN USERNAME PASSWORD LOCAL_PATH SMBCQUOTAS
+EOF
+exit 1;
+fi
+
+SERVER=$1
+DOMAIN=$2
+USERNAME=$3
+PASSWORD=$4
+ENVDIR=`dirname $5`
+SMBCQUOTAS="$VALGRIND $6"
+shift 6
+
+TEST_SMBCQUOTAS=`dirname $0`/test_smbcquota.py
+
+incdir=`dirname $0`/../../../testprogs/blackbox
+. $incdir/subunit.sh
+
+
+testit "smbcquotas" ${TEST_SMBCQUOTAS} ${SERVER} ${DOMAIN} ${USERNAME} ${PASSWORD} ${ENVDIR} ${SMBCQUOTAS} || failed=`expr $failed + 1`
+
+testok $0 $failed
diff --git a/source3/selftest/tests.py b/source3/selftest/tests.py
index 54f5b7b..60d62de 100755
--- a/source3/selftest/tests.py
+++ b/source3/selftest/tests.py
@@ -216,6 +216,7 @@ for env in ["fileserver"]:
     plantestsuite("samba3.blackbox.preserve_case (%s)" % env, env, [os.path.join(samba3srcdir, "script/tests/test_preserve_case.sh"), '$SERVER', '$DOMAIN', '$USERNAME', '$PASSWORD', '$PREFIX', smbclient3])
     plantestsuite("samba3.blackbox.dfree_command (%s)" % env, env, [os.path.join(samba3srcdir, "script/tests/test_dfree_command.sh"), '$SERVER', '$DOMAIN', '$USERNAME', '$PASSWORD', '$PREFIX', smbclient3])
     plantestsuite("samba3.blackbox.dfree_quota (%s)" % env, env, [os.path.join(samba3srcdir, "script/tests/test_dfree_quota.sh"), '$SERVER', '$DOMAIN', '$USERNAME', '$PASSWORD', '$LOCAL_PATH', smbclient3, smbcquotas, smbcacls])
+    plantestsuite("samba3.blackbox.smbcquotas (%s)" % env, env, [os.path.join(samba3srcdir, "script/tests/test_smbcquota.sh"), '$SERVER', '$DOMAIN', '$USERNAME', '$PASSWORD', '$LOCAL_PATH', smbcquotas])
     plantestsuite("samba3.blackbox.valid_users (%s)" % env, env, [os.path.join(samba3srcdir, "script/tests/test_valid_users.sh"), '$SERVER', '$SERVER_IP', '$DOMAIN', '$USERNAME', '$PASSWORD', '$PREFIX', smbclient3])
     plantestsuite("samba3.blackbox.offline (%s)" % env, env, [os.path.join(samba3srcdir, "script/tests/test_offline.sh"), '$SERVER', '$SERVER_IP', '$DOMAIN', '$USERNAME', '$PASSWORD', '$LOCAL_PATH/offline', smbclient3])
     plantestsuite("samba3.blackbox.shadow_copy2 NT1 (%s)" % env, env, [os.path.join(samba3srcdir, "script/tests/test_shadow_copy.sh"), '$SERVER', '$SERVER_IP', '$DOMAIN', '$USERNAME', '$PASSWORD', '$LOCAL_PATH/shadow', smbclient3, '-m', 'NT1'])
-- 
2.10.2


From b96fefa42832c22d63d878bf9e4aa6335cc9924b Mon Sep 17 00:00:00 2001
From: Noel Power <noel.power at suse.com>
Date: Wed, 22 Mar 2017 20:06:13 +0000
Subject: [PATCH 14/14] s3/smbd: allow set quota for non root user (when built
 with --enable-selftest)

Currently it appears you need to be root to set quotas, for test purposes
this requirement needs to be relaxed.

Signed-off-by: Noel Power <noel.power at suse.com>
---
 source3/smbd/nttrans.c | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/source3/smbd/nttrans.c b/source3/smbd/nttrans.c
index e622578..fca049a 100644
--- a/source3/smbd/nttrans.c
+++ b/source3/smbd/nttrans.c
@@ -2763,7 +2763,11 @@ static void call_nt_transact_set_user_quota(connection_struct *conn,
 	ZERO_STRUCT(qt);
 
 	/* access check */
+#if ENABLE_SELFTEST
+	if (get_current_uid(conn) != sec_initial_uid()) {
+#else
 	if (get_current_uid(conn) != 0) {
+#endif
 		DEBUG(1,("set_user_quota: access_denied service [%s] user "
 			 "[%s]\n", lp_servicename(talloc_tos(), SNUM(conn)),
 			 conn->session_info->unix_info->unix_name));
-- 
2.10.2



More information about the samba-technical mailing list