[PATCHES]smbd: dfree quota principal parameter

Uri Simchoni uri at samba.org
Thu Jan 28 10:38:46 UTC 2016


Hi,
Following patch is meant to increase the usability of the "inherit 
owner" parameter in a system where user quotas are being enforced.

Some client applications (such as Windows Explorer and Notepad) would 
not attempt to create a file if there isn't enough free disk space. The 
free space is reported based on file system free space and user quota. 
However, for shares with "inherit owner", the file is eventually owned 
by a different user, so the quota enforcement is that of the folder's 
owner, not that of the logged-on user.

A new parameter "dfree quota principal" allows for calculating the quota 
based on directory owner.

Also sneaked into this patch set is a small unrelated cleanup.

Review appreciated.
Thanks,
Uri.

-------------- next part --------------
From 6badd1b106e51c9d3ba4d81521e7293094820634 Mon Sep 17 00:00:00 2001
From: Uri Simchoni <uri at samba.org>
Date: Wed, 27 Jan 2016 08:12:20 +0200
Subject: [PATCH 1/5] quotas: small cleanup

Remove an internal function from proto.h

Signed-off-by: Uri Simchoni <uri at samba.org>
---
 source3/smbd/proto.h  | 1 -
 source3/smbd/quotas.c | 6 ++++--
 2 files changed, 4 insertions(+), 3 deletions(-)

diff --git a/source3/smbd/proto.h b/source3/smbd/proto.h
index 9b9c924..2f8999a 100644
--- a/source3/smbd/proto.h
+++ b/source3/smbd/proto.h
@@ -831,7 +831,6 @@ bool fork_echo_handler(struct smbXsrv_connection *xconn);
 
 bool disk_quotas(connection_struct *conn, const char *path, uint64_t *bsize,
 		 uint64_t *dfree, uint64_t *dsize);
-bool disk_quotas_vxfs(const char *name, char *path, uint64_t *bsize, uint64_t *dfree, uint64_t *dsize);
 
 /* The following definitions come from smbd/reply.c  */
 
diff --git a/source3/smbd/quotas.c b/source3/smbd/quotas.c
index 8e41416..da2611e 100644
--- a/source3/smbd/quotas.c
+++ b/source3/smbd/quotas.c
@@ -47,7 +47,8 @@
  * Declare here, define at end: reduces likely "include" interaction problems.
  *	David Lee <T.D.Lee at durham.ac.uk>
  */
-bool disk_quotas_vxfs(const char *name, char *path, uint64_t *bsize, uint64_t *dfree, uint64_t *dsize);
+static bool disk_quotas_vxfs(const char *name, char *path, uint64_t *bsize,
+			     uint64_t *dfree, uint64_t *dsize);
 
 #endif /* VXFS_QUOTA */
 
@@ -573,7 +574,8 @@ Hints for porting:
 #include <sys/fs/vx_aioctl.h>
 #include <sys/fs/vx_ioctl.h>
 
-bool disk_quotas_vxfs(const char *name, char *path, uint64_t *bsize, uint64_t *dfree, uint64_t *dsize)
+static bool disk_quotas_vxfs(const char *name, char *path, uint64_t *bsize,
+			     uint64_t *dfree, uint64_t *dsize)
 {
   uid_t user_id, euser_id;
   int ret;
-- 
2.4.3


From ae4510931743499e86295d454d63e9e31bd29aa4 Mon Sep 17 00:00:00 2001
From: Uri Simchoni <uri at samba.org>
Date: Thu, 14 Jan 2016 00:09:36 +0200
Subject: [PATCH 2/5] smbd: get a valid file stat to disk_quotas

Most calls to disk_quotas originate at a state with an
open file descriptor. Pass the file's stat info down to
disk_quota, so that we can avoid extra stat's and the related
error handling.

Signed-off-by: Uri Simchoni <uri at samba.org>
---
 source3/modules/vfs_ceph.c    |  5 ++---
 source3/modules/vfs_default.c |  5 ++---
 source3/smbd/dfree.c          | 16 +++++++---------
 source3/smbd/proto.h          | 15 ++++++---------
 source3/smbd/quotas.c         | 32 ++++++++++++++++----------------
 source3/smbd/reply.c          | 13 ++++++++++++-
 source3/smbd/trans2.c         | 12 ++++++------
 source3/smbd/vfs.c            |  4 ++--
 8 files changed, 53 insertions(+), 49 deletions(-)

diff --git a/source3/modules/vfs_ceph.c b/source3/modules/vfs_ceph.c
index d51499d..2c02ae5 100644
--- a/source3/modules/vfs_ceph.c
+++ b/source3/modules/vfs_ceph.c
@@ -817,9 +817,8 @@ static int strict_allocate_ftruncate(struct vfs_handle_struct *handle, files_str
 		"error %d. Falling back to slow manual allocation\n", errno));
 
 	/* available disk space is enough or not? */
-	space_avail = get_dfree_info(fsp->conn,
-				     fsp->fsp_name->base_name,
-				     &bsize, &dfree, &dsize);
+	space_avail =
+	    get_dfree_info(fsp->conn, fsp->fsp_name, &bsize, &dfree, &dsize);
 	/* space_avail is 1k blocks */
 	if (space_avail == (uint64_t)-1 ||
 			((uint64_t)space_to_write/1024 > space_avail) ) {
diff --git a/source3/modules/vfs_default.c b/source3/modules/vfs_default.c
index 762624b..62c6482 100644
--- a/source3/modules/vfs_default.c
+++ b/source3/modules/vfs_default.c
@@ -1936,9 +1936,8 @@ static int strict_allocate_ftruncate(vfs_handle_struct *handle, files_struct *fs
 		"error %d. Falling back to slow manual allocation\n", errno));
 
 	/* available disk space is enough or not? */
-	space_avail = get_dfree_info(fsp->conn,
-				     fsp->fsp_name->base_name,
-				     &bsize, &dfree, &dsize);
+	space_avail =
+	    get_dfree_info(fsp->conn, fsp->fsp_name, &bsize, &dfree, &dsize);
 	/* space_avail is 1k blocks */
 	if (space_avail == (uint64_t)-1 ||
 			((uint64_t)space_to_write/1024 > space_avail) ) {
diff --git a/source3/smbd/dfree.c b/source3/smbd/dfree.c
index 765fbe6..c1c42eb 100644
--- a/source3/smbd/dfree.c
+++ b/source3/smbd/dfree.c
@@ -49,7 +49,7 @@ static void disk_norm(uint64_t *bsize, uint64_t *dfree, uint64_t *dsize)
  Return number of 1K blocks available on a path and total number.
 ****************************************************************************/
 
-uint64_t sys_disk_free(connection_struct *conn, const char *path,
+uint64_t sys_disk_free(connection_struct *conn, struct smb_filename *fname,
 		       uint64_t *bsize, uint64_t *dfree, uint64_t *dsize)
 {
 	uint64_t dfree_retval;
@@ -58,6 +58,7 @@ uint64_t sys_disk_free(connection_struct *conn, const char *path,
 	uint64_t dsize_q = 0;
 	const char *dfree_command;
 	static bool dfree_broken = false;
+	const char *path = fname->base_name;
 
 	(*dfree) = (*dsize) = 0;
 	(*bsize) = 512;
@@ -123,7 +124,7 @@ uint64_t sys_disk_free(connection_struct *conn, const char *path,
 		return (uint64_t)-1;
 	}
 
-	if (disk_quotas(conn, path, &bsize_q, &dfree_q, &dsize_q)) {
+	if (disk_quotas(conn, fname, &bsize_q, &dfree_q, &dsize_q)) {
 		uint64_t min_bsize = MIN(*bsize, bsize_q);
 
 		(*dfree) = (*dfree) * (*bsize) / min_bsize;
@@ -167,18 +168,15 @@ dfree_done:
  Potentially returned cached dfree info.
 ****************************************************************************/
 
-uint64_t get_dfree_info(connection_struct *conn,
-			const char *path,
-			uint64_t *bsize,
-			uint64_t *dfree,
-			uint64_t *dsize)
+uint64_t get_dfree_info(connection_struct *conn, struct smb_filename *fname,
+			uint64_t *bsize, uint64_t *dfree, uint64_t *dsize)
 {
 	int dfree_cache_time = lp_dfree_cache_time(SNUM(conn));
 	struct dfree_cached_info *dfc = conn->dfree_info;
 	uint64_t dfree_ret;
 
 	if (!dfree_cache_time) {
-		return sys_disk_free(conn, path, bsize, dfree, dsize);
+		return sys_disk_free(conn, fname, bsize, dfree, dsize);
 	}
 
 	if (dfc && (conn->lastused - dfc->last_dfree_time < dfree_cache_time)) {
@@ -189,7 +187,7 @@ uint64_t get_dfree_info(connection_struct *conn,
 		return dfc->dfree_ret;
 	}
 
-	dfree_ret = sys_disk_free(conn, path, bsize, dfree, dsize);
+	dfree_ret = sys_disk_free(conn, fname, bsize, dfree, dsize);
 
 	if (dfree_ret == (uint64_t)-1) {
 		/* Don't cache bad data. */
diff --git a/source3/smbd/proto.h b/source3/smbd/proto.h
index 2f8999a..b7dda06 100644
--- a/source3/smbd/proto.h
+++ b/source3/smbd/proto.h
@@ -171,13 +171,10 @@ bool connections_snum_used(struct smbd_server_connection *unused, int snum);
 
 /* The following definitions come from smbd/dfree.c  */
 
-uint64_t sys_disk_free(connection_struct *conn, const char *path,
-                              uint64_t *bsize,uint64_t *dfree,uint64_t *dsize);
-uint64_t get_dfree_info(connection_struct *conn,
-			const char *path,
-			uint64_t *bsize,
-			uint64_t *dfree,
-			uint64_t *dsize);
+uint64_t sys_disk_free(connection_struct *conn, struct smb_filename *fname,
+		       uint64_t *bsize, uint64_t *dfree, uint64_t *dsize);
+uint64_t get_dfree_info(connection_struct *conn, struct smb_filename *fname,
+			uint64_t *bsize, uint64_t *dfree, uint64_t *dsize);
 
 /* The following definitions come from smbd/dir.c  */
 
@@ -829,8 +826,8 @@ bool fork_echo_handler(struct smbXsrv_connection *xconn);
 
 /* The following definitions come from smbd/quotas.c  */
 
-bool disk_quotas(connection_struct *conn, const char *path, uint64_t *bsize,
-		 uint64_t *dfree, uint64_t *dsize);
+bool disk_quotas(connection_struct *conn, struct smb_filename *fname,
+		 uint64_t *bsize, uint64_t *dfree, uint64_t *dsize);
 
 /* The following definitions come from smbd/reply.c  */
 
diff --git a/source3/smbd/quotas.c b/source3/smbd/quotas.c
index da2611e..79b622d 100644
--- a/source3/smbd/quotas.c
+++ b/source3/smbd/quotas.c
@@ -237,8 +237,8 @@ try to get the disk space from disk quotas (SunOS & Solaris2 version)
 Quota code by Peter Urbanec (amiga at cse.unsw.edu.au).
 ****************************************************************************/
 
-bool disk_quotas(connection_struct *conn, const char *path, uint64_t *bsize,
-		 uint64_t *dfree, uint64_t *dsize)
+bool disk_quotas(connection_struct *conn, struct smb_filename *fname,
+		 uint64_t *bsize, uint64_t *dfree, uint64_t *dsize)
 {
 	uid_t euser_id;
 	int ret;
@@ -255,14 +255,11 @@ bool disk_quotas(connection_struct *conn, const char *path, uint64_t *bsize,
 	SMB_STRUCT_STAT sbuf;
 	SMB_DEV_T devno;
 	bool found = false;
+	const char *path = fname->base_name;
 
 	euser_id = geteuid();
 
-	if (sys_stat(path, &sbuf, false) == -1) {
-		return false;
-	}
-
-	devno = sbuf.st_ex_dev ;
+	devno = fname->st.st_ex_dev;
 	DEBUG(5,("disk_quotas: looking for path \"%s\" devno=%x\n",
 		path, (unsigned int)devno));
 #if defined(SUNOS5)
@@ -427,15 +424,16 @@ bool disk_quotas(connection_struct *conn, const char *path, uint64_t *bsize,
 try to get the disk space from disk quotas - default version
 ****************************************************************************/
 
-bool disk_quotas(connection_struct *conn, const char *path, uint64_t *bsize,
-		 uint64_t *dfree, uint64_t *dsize)
+bool disk_quotas(connection_struct *conn, struct smb_filename *fname,
+		 uint64_t *bsize, uint64_t *dfree, uint64_t *dsize)
 {
   int r;
   struct dqblk D;
   uid_t euser_id;
+  const char *path = fname->base_name;
 #if !defined(AIX)
   char dev_disk[256];
-  SMB_STRUCT_STAT S;
+  SMB_STRUCT_STAT S = fname->st;
 
   /* find the block device file */
 
@@ -659,8 +657,8 @@ static bool disk_quotas_vxfs(const char *name, char *path, uint64_t *bsize,
 
 #else /* WITH_QUOTAS */
 
-bool disk_quotas(connection_struct *conn, const char *path, uint64_t *bsize,
-		 uint64_t *dfree, uint64_t *dsize)
+bool disk_quotas(connection_struct *conn, struct smb_filename *fname,
+		 uint64_t *bsize, uint64_t *dfree, uint64_t *dsize)
 {
 	(*bsize) = 512; /* This value should be ignored */
 
@@ -678,8 +676,8 @@ bool disk_quotas(connection_struct *conn, const char *path, uint64_t *bsize,
 /* wrapper to the new sys_quota interface
    this file should be removed later
    */
-bool disk_quotas(connection_struct *conn, const char *path, uint64_t *bsize,
-		 uint64_t *dfree, uint64_t *dsize)
+bool disk_quotas(connection_struct *conn, struct smb_filename *fname,
+		 uint64_t *bsize, uint64_t *dfree, uint64_t *dsize)
 {
 	int r;
 	SMB_DISK_QUOTA D;
@@ -688,7 +686,8 @@ bool disk_quotas(connection_struct *conn, const char *path, uint64_t *bsize,
 	id.uid = geteuid();
 
 	ZERO_STRUCT(D);
-	r = SMB_VFS_GET_QUOTA(conn, path, SMB_USER_QUOTA_TYPE, id, &D);
+	r = SMB_VFS_GET_QUOTA(conn, fname->base_name, SMB_USER_QUOTA_TYPE, id,
+			      &D);
 
 	/* Use softlimit to determine disk space, except when it has been exceeded */
 	*bsize = D.bsize;
@@ -727,7 +726,8 @@ try_group_quota:
 	id.gid = getegid();
 
 	ZERO_STRUCT(D);
-	r = SMB_VFS_GET_QUOTA(conn, path, SMB_GROUP_QUOTA_TYPE, id, &D);
+	r = SMB_VFS_GET_QUOTA(conn, fname->base_name, SMB_GROUP_QUOTA_TYPE, id,
+			      &D);
 
 	/* Use softlimit to determine disk space, except when it has been exceeded */
 	*bsize = D.bsize;
diff --git a/source3/smbd/reply.c b/source3/smbd/reply.c
index 77d5b6e..2ffa481 100644
--- a/source3/smbd/reply.c
+++ b/source3/smbd/reply.c
@@ -1573,9 +1573,20 @@ void reply_dskattr(struct smb_request *req)
 	connection_struct *conn = req->conn;
 	uint64_t ret;
 	uint64_t dfree,dsize,bsize;
+	struct smb_filename smb_fname;
 	START_PROFILE(SMBdskattr);
 
-	ret = get_dfree_info(conn, ".", &bsize, &dfree, &dsize);
+	ZERO_STRUCT(smb_fname);
+	smb_fname.base_name = discard_const_p(char, ".");
+
+	if (SMB_VFS_STAT(conn, &smb_fname) != 0) {
+		reply_nterror(req, map_nt_error_from_unix(errno));
+		DBG_WARNING("stat of . failed (%s)\n", strerror(errno));
+		END_PROFILE(SMBdskattr);
+		return;
+	}
+
+	ret = get_dfree_info(conn, &smb_fname, &bsize, &dfree, &dsize);
 	if (ret == (uint64_t)-1) {
 		reply_nterror(req, map_nt_error_from_unix(errno));
 		END_PROFILE(SMBdskattr);
diff --git a/source3/smbd/trans2.c b/source3/smbd/trans2.c
index 75be763..2c3c14a 100644
--- a/source3/smbd/trans2.c
+++ b/source3/smbd/trans2.c
@@ -3338,8 +3338,8 @@ NTSTATUS smbd_do_qfsinfo(struct smbXsrv_connection *xconn,
 		{
 			uint64_t dfree,dsize,bsize,block_size,sectors_per_unit;
 			data_len = 18;
-			df_ret = get_dfree_info(conn, filename, &bsize, &dfree,
-						&dsize);
+			df_ret = get_dfree_info(conn, &smb_fname, &bsize,
+						&dfree, &dsize);
 			if (df_ret == (uint64_t)-1) {
 				return map_nt_error_from_unix(errno);
 			}
@@ -3489,8 +3489,8 @@ cBytesSector=%u, cUnitTotal=%u, cUnitAvail=%d\n", (unsigned int)st.st_ex_dev, (u
 		{
 			uint64_t dfree,dsize,bsize,block_size,sectors_per_unit;
 			data_len = 24;
-			df_ret = get_dfree_info(conn, filename, &bsize, &dfree,
-						&dsize);
+			df_ret = get_dfree_info(conn, &smb_fname, &bsize,
+						&dfree, &dsize);
 			if (df_ret == (uint64_t)-1) {
 				return map_nt_error_from_unix(errno);
 			}
@@ -3523,8 +3523,8 @@ cBytesSector=%u, cUnitTotal=%u, cUnitAvail=%d\n", (unsigned int)bsize, (unsigned
 		{
 			uint64_t dfree,dsize,bsize,block_size,sectors_per_unit;
 			data_len = 32;
-			df_ret = get_dfree_info(conn, filename, &bsize, &dfree,
-						&dsize);
+			df_ret = get_dfree_info(conn, &smb_fname, &bsize,
+						&dfree, &dsize);
 			if (df_ret == (uint64_t)-1) {
 				return map_nt_error_from_unix(errno);
 			}
diff --git a/source3/smbd/vfs.c b/source3/smbd/vfs.c
index 89f0ae1..93726bd 100644
--- a/source3/smbd/vfs.c
+++ b/source3/smbd/vfs.c
@@ -593,8 +593,8 @@ int vfs_allocate_file_space(files_struct *fsp, uint64_t len)
 
 	len -= fsp->fsp_name->st.st_ex_size;
 	len /= 1024; /* Len is now number of 1k blocks needed. */
-	space_avail = get_dfree_info(conn, fsp->fsp_name->base_name,
-				     &bsize, &dfree, &dsize);
+	space_avail =
+	    get_dfree_info(conn, fsp->fsp_name, &bsize, &dfree, &dsize);
 	if (space_avail == (uint64_t)-1) {
 		return -1;
 	}
-- 
2.4.3


From a34157851421d43dfb8ca2c565aa764df76ffb75 Mon Sep 17 00:00:00 2001
From: Uri Simchoni <uri at samba.org>
Date: Wed, 20 Jan 2016 22:08:50 +0200
Subject: [PATCH 3/5] smbd: add dfree quota principal parameter

This parameter allows calculation of quota based on
folder owner rather than the user making the request

See added documentation for the rationale behind this
parameter.

Signed-off-by: Uri Simchoni <uri at samba.org>
---
 docs-xml/smbdotconf/misc/dfreequotaprincipal.xml | 45 ++++++++++++++++++++++++
 lib/param/loadparm.c                             |  2 ++
 lib/param/loadparm.h                             |  3 ++
 lib/param/param_table.c                          |  4 +++
 source3/smbd/quotas.c                            | 21 ++++++++---
 5 files changed, 71 insertions(+), 4 deletions(-)
 create mode 100644 docs-xml/smbdotconf/misc/dfreequotaprincipal.xml

diff --git a/docs-xml/smbdotconf/misc/dfreequotaprincipal.xml b/docs-xml/smbdotconf/misc/dfreequotaprincipal.xml
new file mode 100644
index 0000000..4b9c4e6
--- /dev/null
+++ b/docs-xml/smbdotconf/misc/dfreequotaprincipal.xml
@@ -0,0 +1,45 @@
+<samba:parameter name="dfree quota principal"
+                 context="S"
+                 type="enum"
+                 enumlist="enum_dfree_quota_principal_vals"
+                 xmlns:samba="http://www.samba.org/samba/DTD/samba-doc">
+<description>
+
+    <para>This parameter controls the uid on which to query user
+    quota when determining free disk space on a path. Possible values
+    are <emphasis>user</emphasis>, and <emphasis>owner</emphasis>.
+    </para>
+
+    <para>When set to <emphasis>user</emphasis>, user quota calculation
+    is be based on the logged-on user.</para>
+
+    <para>When set to <emphasis>owner</emphasis>, user quota calculation
+    is based on the owner of the path on which the query is made.</para>
+
+    <para>In SMB2 protocol, a client may query disk size and free
+    space relative to some path. Such is the case when issuing a
+    "dir" command in the Windows command prompt - the query is
+    relative to the current directory. In SMB1 protocol, the query
+    is always for the underlying file system as a whole, and
+    <citerefentry><refentrytitle>smbd</refentrytitle><manvolnum>8</manvolnum>
+    </citerefentry> performs the calculation based on the share's root
+    directory.
+    </para>
+
+    <para>Given a path (either provided by the user or the default share root
+    for SMB1), and asked to calculate disk size and free space,
+    <citerefentry><refentrytitle>smbd</refentrytitle><manvolnum>8</manvolnum>
+    </citerefentry> would return the lesser of the actual file system
+    stats and the quota limits in place. Usually, the relevant quotas
+    are those of the user making the request. However, when
+    <smbconfoption name="inherit owner"/> is enabled, files created
+    under a directory would have the directory owner as their owner,
+    hence the relevant user quota is that of the directory owner.
+    Setting dfree quota principal to <emphasis>owner</emphasis> would
+    use the owner of the supplied path as the user ID for user quota
+    calculation.
+    </para>
+</description>
+
+<value type="default">user</value>
+</samba:parameter>
diff --git a/lib/param/loadparm.c b/lib/param/loadparm.c
index 9a3451f..a37d036 100644
--- a/lib/param/loadparm.c
+++ b/lib/param/loadparm.c
@@ -2889,6 +2889,8 @@ struct loadparm_context *loadparm_init(TALLOC_CTX *mem_ctx)
 
 	lpcfg_do_global_parameter(lp_ctx, "aio max threads", "100");
 
+	lpcfg_do_global_parameter(lp_ctx, "dfree quota principal", "user");
+
 	/* Allow modules to adjust defaults */
 	for (defaults_hook = defaults_hooks; defaults_hook;
 		 defaults_hook = defaults_hook->next) {
diff --git a/lib/param/loadparm.h b/lib/param/loadparm.h
index b453aca..d9b2957 100644
--- a/lib/param/loadparm.h
+++ b/lib/param/loadparm.h
@@ -219,6 +219,9 @@ enum mapreadonly_options {MAP_READONLY_NO, MAP_READONLY_YES, MAP_READONLY_PERMIS
 /* case handling */
 enum case_handling {CASE_LOWER,CASE_UPPER};
 
+/* disk free user quota source */
+enum quota_principal { QUOTA_PRINCIPAL_USER, QUOTA_PRINCIPAL_OWNER };
+
 /*
  * Default passwd chat script.
  */
diff --git a/lib/param/param_table.c b/lib/param/param_table.c
index 1ebb2f8..5323621 100644
--- a/lib/param/param_table.c
+++ b/lib/param/param_table.c
@@ -272,6 +272,10 @@ static const struct enum_list enum_case[] = {
 	{-1, NULL}
 };
 
+static const struct enum_list enum_dfree_quota_principal_vals[] = {
+    {QUOTA_PRINCIPAL_USER, "user"},
+    {QUOTA_PRINCIPAL_OWNER, "owner"},
+    {-1, NULL}};
 
 /* Note: We do not initialise the defaults union - it is not allowed in ANSI C
  *
diff --git a/source3/smbd/quotas.c b/source3/smbd/quotas.c
index 79b622d..7c0fa4b 100644
--- a/source3/smbd/quotas.c
+++ b/source3/smbd/quotas.c
@@ -683,11 +683,24 @@ bool disk_quotas(connection_struct *conn, struct smb_filename *fname,
 	SMB_DISK_QUOTA D;
 	unid_t id;
 
-	id.uid = geteuid();
-
 	ZERO_STRUCT(D);
-	r = SMB_VFS_GET_QUOTA(conn, fname->base_name, SMB_USER_QUOTA_TYPE, id,
-			      &D);
+	id.uid = geteuid();
+	if (lp_dfree_quota_principal(SNUM(conn)) == QUOTA_PRINCIPAL_OWNER &&
+	    id.uid != fname->st.st_ex_uid &&
+	    id.uid != sec_initial_uid()) {
+		int save_errno;
+
+		id.uid = fname->st.st_ex_uid;
+		become_root();
+		r = SMB_VFS_GET_QUOTA(conn, fname->base_name,
+				      SMB_USER_QUOTA_TYPE, id, &D);
+		save_errno = errno;
+		unbecome_root();
+		errno = save_errno;
+	} else {
+		r = SMB_VFS_GET_QUOTA(conn, fname->base_name,
+				      SMB_USER_QUOTA_TYPE, id, &D);
+	}
 
 	/* Use softlimit to determine disk space, except when it has been exceeded */
 	*bsize = D.bsize;
-- 
2.4.3


From 0f6abf3371acb05d8dbe90a6c1bfcafef8b69f99 Mon Sep 17 00:00:00 2001
From: Uri Simchoni <uri at samba.org>
Date: Wed, 20 Jan 2016 21:54:24 +0200
Subject: [PATCH 4/5] selftest: refactor test_dfree_quota.sh - add share
 parameter

Add a share parameter to individual disk-free tests. This will
allow running tests on shares other than dfq share.

Signed-off-by: Uri Simchoni <uri at samba.org>
---
 source3/script/tests/test_dfree_quota.sh | 45 ++++++++++++++++----------------
 1 file changed, 23 insertions(+), 22 deletions(-)

diff --git a/source3/script/tests/test_dfree_quota.sh b/source3/script/tests/test_dfree_quota.sh
index 6240b56..813d206 100755
--- a/source3/script/tests/test_dfree_quota.sh
+++ b/source3/script/tests/test_dfree_quota.sh
@@ -95,16 +95,17 @@ setup_conf() {
 
 test_smbclient_dfree() {
 	name="$1"
-	dir="$2"
-    confs="$3"
-    expected="$4"
-	shift
+    share="$2"
+    dir="$3"
+    confs="$4"
+    expected="$5"
+    shift
     shift
     shift
     shift
     subunit_start_test "$name"
     setup_conf $confs
-	output=$($VALGRIND $smbclient //$SERVER/dfq -c "cd $dir; l" $@ 2>&1)
+    output=$($VALGRIND $smbclient //$SERVER/$share -c "cd $dir; l" $@ 2>&1)
     status=$?
     if [ "$status" = "0" ]; then
 		received=$(echo "$output" | awk '/blocks of size/ {print $1, $5, $6}')
@@ -146,34 +147,34 @@ test_smbcquotas() {
 }
 
 #basic disk-free tests
-test_smbclient_dfree "Test dfree share root SMB3 no quota" "." "conf1 ." "10 1024. 5" -U$USERNAME%$PASSWORD --option=clientmaxprotocol=SMB3 || failed=`expr $failed + 1`
-test_smbclient_dfree "Test dfree subdir SMB3 no quota" "subdir1" "conf1 . conf2 subdir1" "20 1024. 10" -U$USERNAME%$PASSWORD --option=clientmaxprotocol=SMB3 || failed=`expr $failed + 1`
-test_smbclient_dfree "Test dfree subdir NT1 no quota" "subdir1" "conf1 . conf2 subdir1" "10 1024. 5" -U$USERNAME%$PASSWORD --option=clientmaxprotocol=NT1 || failed=`expr $failed + 1`
-test_smbclient_dfree "Test large disk" "." "conf3 ." "1125899906842624 1024. 3000" -U$USERNAME%$PASSWORD --option=clientmaxprotocol=SMB3 || failed=`expr $failed + 1`
+test_smbclient_dfree "Test dfree share root SMB3 no quota" dfq "." "conf1 ." "10 1024. 5" -U$USERNAME%$PASSWORD --option=clientmaxprotocol=SMB3 || failed=`expr $failed + 1`
+test_smbclient_dfree "Test dfree subdir SMB3 no quota" dfq "subdir1" "conf1 . conf2 subdir1" "20 1024. 10" -U$USERNAME%$PASSWORD --option=clientmaxprotocol=SMB3 || failed=`expr $failed + 1`
+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`
 
 #quota limit > disk size, remaining quota > disk free
-test_smbclient_dfree "Test dfree share root df vs quota case 1" "." "confdfq1 ." "80 1024. 40" -U$USERNAME%$PASSWORD --option=clientmaxprotocol=SMB3 || failed=`expr $failed + 1`
+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`
 #quota limit > disk size, remaining quota < disk free
-test_smbclient_dfree "Test dfree share root df vs quota case 2" "." "confdfq2 ." "80 1024. 12" -U$USERNAME%$PASSWORD --option=clientmaxprotocol=SMB3 || failed=`expr $failed + 1`
+test_smbclient_dfree "Test dfree share root df vs quota case 2" dfq "." "confdfq2 ." "80 1024. 12" -U$USERNAME%$PASSWORD --option=clientmaxprotocol=SMB3 || failed=`expr $failed + 1`
 #quota limit < disk size, remaining quota > disk free
-test_smbclient_dfree "Test dfree share root df vs quota case 3" "." "confdfq3 ." "160 1024. 40" -U$USERNAME%$PASSWORD --option=clientmaxprotocol=SMB3 || failed=`expr $failed + 1`
+test_smbclient_dfree "Test dfree share root df vs quota case 3" dfq "." "confdfq3 ." "160 1024. 40" -U$USERNAME%$PASSWORD --option=clientmaxprotocol=SMB3 || failed=`expr $failed + 1`
 #quota limit < disk size, remaining quota < disk free
-test_smbclient_dfree "Test dfree share root df vs quota case 4" "." "confdfq4 ." "160 1024. 12" -U$USERNAME%$PASSWORD --option=clientmaxprotocol=SMB3 || failed=`expr $failed + 1`
-test_smbclient_dfree "Test dfree subdir df vs quota case 4" "subdir1" "confdfq4 subdir1" "160 1024. 12" -U$USERNAME%$PASSWORD --option=clientmaxprotocol=SMB3 || failed=`expr $failed + 1`
+test_smbclient_dfree "Test dfree share root df vs quota case 4" dfq "." "confdfq4 ." "160 1024. 12" -U$USERNAME%$PASSWORD --option=clientmaxprotocol=SMB3 || failed=`expr $failed + 1`
+test_smbclient_dfree "Test dfree subdir df vs quota case 4" dfq "subdir1" "confdfq4 subdir1" "160 1024. 12" -U$USERNAME%$PASSWORD --option=clientmaxprotocol=SMB3 || failed=`expr $failed + 1`
 
 #quota-->disk free special cases
-test_smbclient_dfree "Test quota->dfree edquot" "subdir1" "edquot subdir1" "164 1024. 0" -U$USERNAME%$PASSWORD --option=clientmaxprotocol=SMB3 || failed=`expr $failed + 1`
-test_smbclient_dfree "Test quota->dfree soft limit" "subdir1" "slimit subdir1" "168 1024. 0" -U$USERNAME%$PASSWORD --option=clientmaxprotocol=SMB3 || failed=`expr $failed + 1`
-test_smbclient_dfree "Test quota->dfree hard limit" "subdir1" "hlimit subdir1" "180 1024. 0" -U$USERNAME%$PASSWORD --option=clientmaxprotocol=SMB3 || failed=`expr $failed + 1`
-test_smbclient_dfree "Test quota->dfree inode soft limit" "subdir1" "islimit subdir1" "148 1024. 0" -U$USERNAME%$PASSWORD --option=clientmaxprotocol=SMB3 || failed=`expr $failed + 1`
-test_smbclient_dfree "Test quota->dfree inode hard limit" "subdir1" "ihlimit subdir1" "148 1024. 0" -U$USERNAME%$PASSWORD --option=clientmaxprotocol=SMB3 || failed=`expr $failed + 1`
-test_smbclient_dfree "Test quota->dfree err try group" "subdir1" "trygrp1 subdir1" "240 1024. 20" -U$USERNAME%$PASSWORD --option=clientmaxprotocol=SMB3 || failed=`expr $failed + 1`
-test_smbclient_dfree "Test quota->dfree no-quota try group" "subdir1" "trygrp2 subdir1" "240 1024. 16" -U$USERNAME%$PASSWORD --option=clientmaxprotocol=SMB3 || failed=`expr $failed + 1`
+test_smbclient_dfree "Test quota->dfree edquot" dfq "subdir1" "edquot subdir1" "164 1024. 0" -U$USERNAME%$PASSWORD --option=clientmaxprotocol=SMB3 || failed=`expr $failed + 1`
+test_smbclient_dfree "Test quota->dfree soft limit" dfq "subdir1" "slimit subdir1" "168 1024. 0" -U$USERNAME%$PASSWORD --option=clientmaxprotocol=SMB3 || failed=`expr $failed + 1`
+test_smbclient_dfree "Test quota->dfree hard limit" dfq "subdir1" "hlimit subdir1" "180 1024. 0" -U$USERNAME%$PASSWORD --option=clientmaxprotocol=SMB3 || failed=`expr $failed + 1`
+test_smbclient_dfree "Test quota->dfree inode soft limit" dfq "subdir1" "islimit subdir1" "148 1024. 0" -U$USERNAME%$PASSWORD --option=clientmaxprotocol=SMB3 || failed=`expr $failed + 1`
+test_smbclient_dfree "Test quota->dfree inode hard limit" dfq "subdir1" "ihlimit subdir1" "148 1024. 0" -U$USERNAME%$PASSWORD --option=clientmaxprotocol=SMB3 || failed=`expr $failed + 1`
+test_smbclient_dfree "Test quota->dfree err try group" dfq "subdir1" "trygrp1 subdir1" "240 1024. 20" -U$USERNAME%$PASSWORD --option=clientmaxprotocol=SMB3 || failed=`expr $failed + 1`
+test_smbclient_dfree "Test quota->dfree no-quota try group" dfq "subdir1" "trygrp2 subdir1" "240 1024. 16" -U$USERNAME%$PASSWORD --option=clientmaxprotocol=SMB3 || failed=`expr $failed + 1`
 
 #block size different in quota and df systems
-test_smbclient_dfree "Test quota->dfree different block size" "subdir1" "blksize subdir1" "307200 1024. 307200" -U$USERNAME%$PASSWORD --option=clientmaxprotocol=SMB3 || failed=`expr $failed + 1`
+test_smbclient_dfree "Test quota->dfree different block size" dfq "subdir1" "blksize subdir1" "307200 1024. 307200" -U$USERNAME%$PASSWORD --option=clientmaxprotocol=SMB3 || failed=`expr $failed + 1`
 
 setup_conf
 exit $failed
-- 
2.4.3


From ef1f1f6770aa9af78fce381fd0bea0144d9c8422 Mon Sep 17 00:00:00 2001
From: Uri Simchoni <uri at samba.org>
Date: Wed, 20 Jan 2016 21:58:43 +0200
Subject: [PATCH 5/5] selftest: Add tests for "dfree quota principal"
 parameter.

Signed-off-by: Uri Simchoni <uri at samba.org>
---
 selftest/selftesthelpers.py              |  1 +
 selftest/target/Samba3.pm                | 39 ++++++++++++++++++++++++++++++--
 source3/script/tests/test_dfree_quota.sh | 37 ++++++++++++++++++++++++++++--
 source3/selftest/tests.py                |  2 +-
 4 files changed, 74 insertions(+), 5 deletions(-)

diff --git a/selftest/selftesthelpers.py b/selftest/selftesthelpers.py
index a5ab3f9..ee8df8f 100644
--- a/selftest/selftesthelpers.py
+++ b/selftest/selftesthelpers.py
@@ -184,3 +184,4 @@ wbinfo = binpath('wbinfo')
 dbwrap_tool = binpath('dbwrap_tool')
 vfstest = binpath('vfstest')
 smbcquotas = binpath('smbcquotas')
+smbcacls = binpath('smbcacls')
diff --git a/selftest/target/Samba3.pm b/selftest/target/Samba3.pm
index 5002a81..e7a444a 100755
--- a/selftest/target/Samba3.pm
+++ b/selftest/target/Samba3.pm
@@ -592,6 +592,7 @@ sub setup_fileserver($$)
 	push(@dirs, $dfree_share_dir);
 	push(@dirs, "$dfree_share_dir/subdir1");
 	push(@dirs, "$dfree_share_dir/subdir2");
+	push(@dirs, "$dfree_share_dir/subdir3");
 
 	my $valid_users_sharedir="$share_dir/valid_users";
 	push(@dirs,$valid_users_sharedir);
@@ -1268,8 +1269,10 @@ sub provision($$$$$$$$)
 	my ($uid_pdbtest_wkn);
 	my ($gid_nobody, $gid_nogroup, $gid_root, $gid_domusers, $gid_domadmins);
 	my ($gid_userdup, $gid_everyone);
+	my ($uid_user1);
+	my ($uid_user2);
 
-	if ($unix_uid < 0xffff - 5) {
+	if ($unix_uid < 0xffff - 8) {
 		$max_uid = 0xffff;
 	} else {
 		$max_uid = $unix_uid;
@@ -1281,6 +1284,8 @@ sub provision($$$$$$$$)
 	$uid_pdbtest2 = $max_uid - 4;
 	$uid_userdup = $max_uid - 5;
 	$uid_pdbtest_wkn = $max_uid - 6;
+	$uid_user1 = $max_uid - 7;
+	$uid_user2 = $max_uid - 8;
 
 	if ($unix_gids[0] < 0xffff - 7) {
 		$max_gid = 0xffff;
@@ -1633,9 +1638,15 @@ sub provision($$$$$$$$)
 	wide links = yes
 [dfq]
 	path = $shrdir/dfree
-	vfs objects = fake_dfq
+	vfs objects = acl_xattr fake_acls xattr_tdb fake_dfq
 	admin users = $unix_name
 	include = $dfqconffile
+[dfq_owner]
+	path = $shrdir/dfree
+	vfs objects = acl_xattr fake_acls xattr_tdb fake_dfq
+	inherit owner = true
+	dfree quota principal = owner
+	include = $dfqconffile
 	";
 	close(CONF);
 
@@ -1659,6 +1670,8 @@ pdbtest:x:$uid_pdbtest:$gid_nogroup:pdbtest gecos:$prefix_abs:/bin/false
 pdbtest2:x:$uid_pdbtest2:$gid_nogroup:pdbtest gecos:$prefix_abs:/bin/false
 userdup:x:$uid_userdup:$gid_userdup:userdup gecos:$prefix_abs:/bin/false
 pdbtest_wkn:x:$uid_pdbtest_wkn:$gid_everyone:pdbtest_wkn gecos:$prefix_abs:/bin/false
+user1:x:$uid_user1:$gid_nogroup:user1 gecos:$prefix_abs:/bin/false
+user2:x:$uid_user2:$gid_nogroup:user2 gecos:$prefix_abs:/bin/false
 ";
 	if ($unix_uid != 0) {
 		print PASSWD "root:x:$uid_root:$gid_root:root gecos:$prefix_abs:/bin/false
@@ -1739,6 +1752,28 @@ everyone:x:$gid_everyone:
              warn("Unable to set password for test account\n$cmd");
              return undef; 
         }
+
+	$cmd = "UID_WRAPPER_ROOT=1 " . Samba::bindir_path($self, "smbpasswd")." -c $conffile -L -s -a user1 > /dev/null";
+	unless (open(PWD, "|$cmd")) {
+	     warn("Unable to set password for user1 account\n$cmd");
+	     return undef;
+	}
+	print PWD "$password\n$password\n";
+	unless (close(PWD)) {
+	     warn("Unable to set password for user1 account\n$cmd");
+	     return undef;
+	}
+
+	$cmd = "UID_WRAPPER_ROOT=1 " . Samba::bindir_path($self, "smbpasswd")." -c $conffile -L -s -a user2 > /dev/null";
+	unless (open(PWD, "|$cmd")) {
+	     warn("Unable to set password for user2 account\n$cmd");
+	     return undef;
+	}
+	print PWD "$password\n$password\n";
+	unless (close(PWD)) {
+	     warn("Unable to set password for user2 account\n$cmd");
+	     return undef;
+	}
 	print "DONE\n";
 
 	open(DNS_UPDATE_LIST, ">$prefix/dns_update_list") or die("Unable to open $$prefix/dns_update_list");
diff --git a/source3/script/tests/test_dfree_quota.sh b/source3/script/tests/test_dfree_quota.sh
index 813d206..9dff6a7 100755
--- a/source3/script/tests/test_dfree_quota.sh
+++ b/source3/script/tests/test_dfree_quota.sh
@@ -5,7 +5,7 @@
 
 if [ $# -lt 6 ]; then
 cat <<EOF
-Usage: test_dfree_quota.sh SERVER DOMAIN USERNAME PASSWORD LOCAL_PATH SMBCLIENT SMBCQUOTAS
+Usage: test_dfree_quota.sh SERVER DOMAIN USERNAME PASSWORD LOCAL_PATH SMBCLIENT SMBCQUOTAS SMBCACLS
 EOF
 exit 1;
 fi
@@ -18,7 +18,8 @@ ENVDIR=`dirname $5`
 WORKDIR=$5/dfree
 smbclient=$6
 smbcquotas=$7
-shift 7
+smbcacls=$8
+shift 8
 failed=0
 
 CONFFILE=$ENVDIR/lib/dfq.conf
@@ -35,6 +36,8 @@ conf_lines() {
     local gid
     uid=$(id -u $USERNAME)
     gid=$(id -g $USERNAME)
+    uid1=$(id -u user1)
+    uid2=$(id -u user2)
 cat <<ABC
 conf1:df:block size = 512:disk free = 10:disk size = 20
 conf2:df:block size = 1024:disk free = 10:disk size = 20
@@ -66,6 +69,9 @@ trygrp2:u$uid:block size = 4096:hard limit = 0:soft limit = 0:cur blocks = 41
 trygrp2:g$gid:block size = 4096:hard limit = 60:soft limit = 60:cur blocks = 56
 blksize:df:block size = 512:disk free = 614400:disk size = 614400
 blksize:u$uid:block size = 1024:hard limit = 512000:soft limit = 0:cur blocks = 0
+confdfqp:df:block size = 4096:disk free = 10:disk size = 80
+confdfqp:u$uid1:block size = 4096:hard limit = 40:soft limit = 40:cur blocks = 36
+confdfqp:u$uid2:block size = 4096:hard limit = 41:soft limit = 41:cur blocks = 36
 ABC
 }
 
@@ -176,5 +182,32 @@ test_smbclient_dfree "Test quota->dfree no-quota try group" dfq "subdir1" "trygr
 #block size different in quota and df systems
 test_smbclient_dfree "Test quota->dfree different block size" dfq "subdir1" "blksize subdir1" "307200 1024. 307200" -U$USERNAME%$PASSWORD --option=clientmaxprotocol=SMB3 || failed=`expr $failed + 1`
 
+#test for dfree quota principal
+#setup two folders with different owners
+rm -rf $WORKDIR/subdir3/*
+for d in / subdir3
+do
+    $VALGRIND $smbcacls -U$USERNAME%$PASSWORD -D "ACL:$SERVER\user1:ALLOWED/0x0/FULL" //$SERVER/dfq $d > /dev/null 2>&1
+    $VALGRIND $smbcacls -U$USERNAME%$PASSWORD -a "ACL:$SERVER\user1:ALLOWED/0x0/FULL" //$SERVER/dfq $d || failed=`expr $failed + 1`
+    $VALGRIND $smbcacls -U$USERNAME%$PASSWORD -D "ACL:$SERVER\user2:ALLOWED/0x0/FULL" //$SERVER/dfq $d > /dev/null 2>&1
+    $VALGRIND $smbcacls -U$USERNAME%$PASSWORD -a "ACL:$SERVER\user2:ALLOWED/0x0/FULL" //$SERVER/dfq $d || failed=`expr $failed + 1`
+done
+
+$VALGRIND $smbclient //$SERVER/dfq -c "cd subdir3; mkdir user1" -Uuser1%$PASSWORD --option=clientmaxprotocol=SMB3 > /dev/null 2>&1 || failed=`expr $failed + 1`
+$VALGRIND $smbclient //$SERVER/dfq -c "cd subdir3; mkdir user2" -Uuser2%$PASSWORD --option=clientmaxprotocol=SMB3 > /dev/null 2>&1 || failed=`expr $failed + 1`
+#test quotas
+test_smbclient_dfree "Test dfree quota principal - user - user1 at user1" \
+    dfq "subdir3/user1" "confdfqp subdir3/user1 confdfqp subdir3/user2" "160 1024. 16" \
+    -Uuser1%$PASSWORD --option=clientmaxprotocol=SMB3 || failed=`expr $failed + 1`
+test_smbclient_dfree "Test dfree quota principal - user - user1 at user2" \
+    dfq "subdir3/user2" "confdfqp subdir3/user1 confdfqp subdir3/user2" "160 1024. 16" \
+    -Uuser1%$PASSWORD --option=clientmaxprotocol=SMB3 || failed=`expr $failed + 1`
+test_smbclient_dfree "Test dfree quota principal - owner - user1 at user1" \
+    dfq_owner "subdir3/user1" "confdfqp subdir3/user1 confdfqp subdir3/user2" "160 1024. 16" \
+    -Uuser1%$PASSWORD --option=clientmaxprotocol=SMB3 || failed=`expr $failed + 1`
+test_smbclient_dfree "Test dfree quota principal - owner - user1 at user2" \
+    dfq_owner "subdir3/user2" "confdfqp subdir3/user1 confdfqp subdir3/user2" "164 1024. 20" \
+    -Uuser1%$PASSWORD --option=clientmaxprotocol=SMB3 || failed=`expr $failed + 1`
+
 setup_conf
 exit $failed
diff --git a/source3/selftest/tests.py b/source3/selftest/tests.py
index 42119cc..47310d9 100755
--- a/source3/selftest/tests.py
+++ b/source3/selftest/tests.py
@@ -177,7 +177,7 @@ for env in ["nt4_dc"]:
 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])
+    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.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 (%s)" % env, env, [os.path.join(samba3srcdir, "script/tests/test_shadow_copy.sh"), '$SERVER', '$SERVER_IP', '$DOMAIN', '$USERNAME', '$PASSWORD', '$LOCAL_PATH/shadow', smbclient3])
-- 
2.4.3



More information about the samba-technical mailing list