allow-arbitrary-params-in-usershares   [plain text]


Index: samba/source/param/loadparm.c
===================================================================
--- samba/source/param/loadparm.c.orig
+++ samba/source/param/loadparm.c
@@ -9,6 +9,7 @@
    Copyright (C) Alexander Bokovoy 2002
    Copyright (C) Stefan (metze) Metzmacher 2002
    Copyright (C) Jim McDonough <jmcd@us.ibm.com> 2003
+   Copyright (C) 2007 Apple Inc. All rights reserved.
    
    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
@@ -305,6 +306,7 @@ typedef struct {
 	BOOL bASUSupport;
 	BOOL bUsershareOwnerOnly;
 	BOOL bUsershareAllowGuests;
+	BOOL bUsershareAllowFullConfig;
 	int restrict_anonymous;
 	int name_cache_timeout;
 	int client_signing;
@@ -1233,6 +1235,7 @@ static struct parm_struct parm_table[] =
 	{"root postexec", P_STRING, P_LOCAL, &sDefault.szRootPostExec, NULL, NULL, FLAG_ADVANCED | FLAG_SHARE | FLAG_PRINT}, 
 	{"available", P_BOOL, P_LOCAL, &sDefault.bAvailable, NULL, NULL, FLAG_BASIC | FLAG_ADVANCED | FLAG_SHARE | FLAG_PRINT}, 
 	{"usershare allow guests", P_BOOL, P_GLOBAL, &Globals.bUsershareAllowGuests, NULL, NULL, FLAG_ADVANCED},
+	{"usershare allow full config", P_BOOL, P_GLOBAL, &Globals.bUsershareAllowFullConfig, NULL, NULL, FLAG_ADVANCED},
 	{"usershare max shares", P_INTEGER, P_GLOBAL, &Globals.iUsershareMaxShares, NULL, NULL, FLAG_ADVANCED},
 	{"usershare owner only", P_BOOL, P_GLOBAL, &Globals.bUsershareOwnerOnly, NULL, NULL, FLAG_ADVANCED}, 
 	{"usershare path", P_STRING, P_GLOBAL, &Globals.szUsersharePath, NULL, NULL, FLAG_ADVANCED},
@@ -1667,6 +1670,8 @@ static void init_globals(BOOL first_time
 	Globals.bUsershareOwnerOnly = True;
 	/* By default disallow guest access to usershares. */
 	Globals.bUsershareAllowGuests = False;
+	/* By default don't allow arbitrary usershare configurations. */
+	Globals.bUsershareAllowFullConfig = False;
 }
 
 static TALLOC_CTX *lp_talloc;
@@ -1922,6 +1927,7 @@ FN_GLOBAL_LIST(lp_usershare_prefix_deny_
 FN_GLOBAL_LIST(lp_eventlog_list, &Globals.szEventLogs)
 
 FN_GLOBAL_BOOL(lp_usershare_allow_guests, &Globals.bUsershareAllowGuests)
+FN_GLOBAL_BOOL(lp_usershare_allow_full_config, &Globals.bUsershareAllowFullConfig)
 FN_GLOBAL_BOOL(lp_usershare_owner_only, &Globals.bUsershareOwnerOnly)
 FN_GLOBAL_BOOL(lp_disable_netbios, &Globals.bDisableNetbios)
 FN_GLOBAL_BOOL(lp_reset_on_zero_vc, &Globals.bResetOnZeroVC)
@@ -4381,84 +4387,33 @@ static BOOL check_usershare_stat(const c
 }
 
 /***************************************************************************
- Parse the contents of a usershare file.
+ Check whether the usershare configuration allows a particular path to be
+ shared.
 ***************************************************************************/
 
-enum usershare_err parse_usershare_file(TALLOC_CTX *ctx, 
-			SMB_STRUCT_STAT *psbuf,
-			const char *servicename,
-			int snum,
-			char **lines,
-			int numlines,
-			pstring sharepath,
-			pstring comment,
-			SEC_DESC **ppsd,
-			BOOL *pallow_guest)
+static enum usershare_err usershare_validate_path(int snum,
+				const char *servicename,
+				const SMB_STRUCT_STAT *svcstat,
+				const char *sharepath)
 {
+	static const char const fn[] = "usershare_validate_path";
+
 	const char **prefixallowlist = lp_usershare_prefix_allow_list();
 	const char **prefixdenylist = lp_usershare_prefix_deny_list();
-	int us_vers;
+
 	SMB_STRUCT_DIR *dp;
 	SMB_STRUCT_STAT sbuf;
 
-	*pallow_guest = False;
-
-	if (numlines < 4) {
-		return USERSHARE_MALFORMED_FILE;
-	}
-
-	if (strcmp(lines[0], "#VERSION 1") == 0) {
-		us_vers = 1;
-	} else if (strcmp(lines[0], "#VERSION 2") == 0) {
-		us_vers = 2;
-		if (numlines < 5) {
-			return USERSHARE_MALFORMED_FILE;
-		}
-	} else {
-		return USERSHARE_BAD_VERSION;
-	}
-
-	if (strncmp(lines[1], "path=", 5) != 0) {
-		return USERSHARE_MALFORMED_PATH;
-	}
-
-	pstrcpy(sharepath, &lines[1][5]);
-	trim_string(sharepath, " ", " ");
-
-	if (strncmp(lines[2], "comment=", 8) != 0) {
-		return USERSHARE_MALFORMED_COMMENT_DEF;
-	}
-
-	pstrcpy(comment, &lines[2][8]);
-	trim_string(comment, " ", " ");
-	trim_char(comment, '"', '"');
-
-	if (strncmp(lines[3], "usershare_acl=", 14) != 0) {
-		return USERSHARE_MALFORMED_ACL_DEF;
-	}
-
-	if (!parse_usershare_acl(ctx, &lines[3][14], ppsd)) {
-		return USERSHARE_ACL_ERR;
-	}
-
-	if (us_vers == 2) {
-		if (strncmp(lines[4], "guest_ok=", 9) != 0) {
-			return USERSHARE_MALFORMED_ACL_DEF;
-		}
-		if (lines[4][9] == 'y') {
-			*pallow_guest = True;
-		}
-	}
-
-	if (snum != -1 && (strcmp(sharepath, ServicePtrs[snum]->szPath) == 0)) {
+	if (snum != GLOBAL_SECTION_SNUM &&
+	    (strcmp(sharepath, ServicePtrs[snum]->szPath) == 0)) {
 		/* Path didn't change, no checks needed. */
 		return USERSHARE_OK;
 	}
 
 	/* The path *must* be absolute. */
 	if (sharepath[0] != '/') {
-		DEBUG(2,("parse_usershare_file: share %s: path %s is not an absolute path.\n",
-			servicename, sharepath));
+		DEBUG(2,("%s: share %s: path %s is not an absolute path.\n",
+			fn, servicename, sharepath));
 		return USERSHARE_PATH_NOT_ABSOLUTE;
 	}
 
@@ -4467,12 +4422,12 @@ enum usershare_err parse_usershare_file(
 	if (prefixdenylist) {
 		int i;
 		for ( i=0; prefixdenylist[i]; i++ ) {
-			DEBUG(10,("parse_usershare_file: share %s : checking prefixdenylist[%d]='%s' against %s\n",
-				servicename, i, prefixdenylist[i], sharepath ));
+			DEBUG(10,("%s: share %s : checking prefixdenylist[%d]='%s' against %s\n",
+				fn, servicename, i, prefixdenylist[i], sharepath ));
 			if (memcmp( sharepath, prefixdenylist[i], strlen(prefixdenylist[i])) == 0) {
-				DEBUG(2,("parse_usershare_file: share %s path %s starts with one of the "
+				DEBUG(2,("%s: share %s path %s starts with one of the "
 					"usershare prefix deny list entries.\n",
-					servicename, sharepath));
+					fn, servicename, sharepath));
 				return USERSHARE_PATH_IS_DENIED;
 			}
 		}
@@ -4484,16 +4439,16 @@ enum usershare_err parse_usershare_file(
 	if (prefixallowlist) {
 		int i;
 		for ( i=0; prefixallowlist[i]; i++ ) {
-			DEBUG(10,("parse_usershare_file: share %s checking prefixallowlist[%d]='%s' against %s\n",
-				servicename, i, prefixallowlist[i], sharepath ));
+			DEBUG(10,("%s: share %s checking prefixallowlist[%d]='%s' against %s\n",
+				fn, servicename, i, prefixallowlist[i], sharepath ));
 			if (memcmp( sharepath, prefixallowlist[i], strlen(prefixallowlist[i])) == 0) {
 				break;
 			}
 		}
 		if (prefixallowlist[i] == NULL) {
-			DEBUG(2,("parse_usershare_file: share %s path %s doesn't start with one of the "
+			DEBUG(2,("%s: share %s path %s doesn't start with one of the "
 				"usershare prefix allow list entries.\n",
-				servicename, sharepath));
+				fn, servicename, sharepath));
 			return USERSHARE_PATH_NOT_ALLOWED;
 		}
         }
@@ -4502,8 +4457,8 @@ enum usershare_err parse_usershare_file(
 	dp = sys_opendir(sharepath);
 
 	if (!dp) {
-		DEBUG(2,("parse_usershare_file: share %s path %s is not a directory.\n",
-			servicename, sharepath));
+		DEBUG(2,("%s: share %s path %s is not a directory.\n",
+			fn, servicename, sharepath));
 		return USERSHARE_PATH_NOT_DIRECTORY;
 	}
 
@@ -4511,8 +4466,8 @@ enum usershare_err parse_usershare_file(
 	   this directory. */
 
 	if (sys_stat(sharepath, &sbuf) == -1) {
-		DEBUG(2,("parse_usershare_file: share %s : stat failed on path %s. %s\n",
-			servicename, sharepath, strerror(errno) ));
+		DEBUG(2,("%s: share %s : stat failed on path %s. %s\n",
+			fn, servicename, sharepath, strerror(errno) ));
 		sys_closedir(dp);
 		return USERSHARE_POSIX_ERR;
 	}
@@ -4520,8 +4475,8 @@ enum usershare_err parse_usershare_file(
 	sys_closedir(dp);
 
 	if (!S_ISDIR(sbuf.st_mode)) {
-		DEBUG(2,("parse_usershare_file: share %s path %s is not a directory.\n",
-			servicename, sharepath ));
+		DEBUG(2,("%s: share %s path %s is not a directory.\n",
+			fn, servicename, sharepath ));
 		return USERSHARE_PATH_NOT_DIRECTORY;
 	}
 
@@ -4531,7 +4486,8 @@ enum usershare_err parse_usershare_file(
 
 	if (lp_usershare_owner_only()) {
 		/* root can share anything. */
-		if ((psbuf->st_uid != 0) && (sbuf.st_uid != psbuf->st_uid)) {
+		if ((svcstat->st_uid != 0) &&
+		    (sbuf.st_uid != svcstat->st_uid)) {
 			return USERSHARE_PATH_NOT_ALLOWED;
 		}
 	}
@@ -4539,6 +4495,282 @@ enum usershare_err parse_usershare_file(
 	return USERSHARE_OK;
 }
 
+/* Handle versions 1 and 2. */
+static enum usershare_err parse_usershare_file_vers2(TALLOC_CTX *ctx,
+			const SMB_STRUCT_STAT *svcstat,
+			const char *servicename,
+			int snum,
+			int us_vers,
+			const char **lines,
+			int numlines)
+{
+	pstring sharepath;
+	pstring comment;
+	SEC_DESC *psd = NULL;
+	BOOL guest_ok = False;
+	BOOL applied_share_acl = False;
+	enum usershare_err ret = USERSHARE_MALFORMED_FILE;
+
+	if (us_vers != 1 && us_vers != 2) {
+		return USERSHARE_BAD_VERSION;
+	}
+
+	if (numlines < 4) {
+		return USERSHARE_MALFORMED_FILE;
+	}
+
+	if (us_vers == 2 && numlines < 5) {
+		return USERSHARE_MALFORMED_FILE;
+	}
+
+	if (strncmp(lines[1], "path=", 5) != 0) {
+		return USERSHARE_MALFORMED_PATH;
+	}
+	pstrcpy(sharepath, &lines[1][5]);
+	trim_string(sharepath, " ", " ");
+
+	ret = usershare_validate_path(snum, servicename, svcstat, sharepath);
+	if (ret != USERSHARE_OK) {
+		goto done;
+	}
+
+	if (snum != GLOBAL_SECTION_SNUM &&
+	    !lp_do_parameter(snum, "path", sharepath)) {
+		ret = USERSHARE_MALFORMED_PATH;
+		goto done;
+	}
+
+	if (strncmp(lines[2], "comment=", 8) != 0) {
+		ret = USERSHARE_MALFORMED_COMMENT_DEF;
+		goto done;
+	}
+
+	pstrcpy(comment, &lines[2][8]);
+	trim_string(comment, " ", " ");
+	trim_char(comment, '"', '"');
+
+	if (snum != GLOBAL_SECTION_SNUM) {
+		lp_do_parameter(snum, "comment", comment);
+	}
+
+	if (strncmp(lines[3], "usershare_acl=", 14) != 0) {
+		ret = USERSHARE_MALFORMED_ACL_DEF;
+		goto done;
+	}
+
+	if (!parse_usershare_acl(ctx, &lines[3][14], &psd)) {
+		ret = USERSHARE_ACL_ERR;
+		goto done;
+	}
+
+	/* Write the ACL of the new/modified share. */
+	if (snum != GLOBAL_SECTION_SNUM) {
+		if (!set_share_security(servicename, psd)) {
+			 DEBUG(0, ("Failed to set share "
+				"security for user share %s\n",
+				servicename ));
+			ret = USERSHARE_ACL_ERR;
+			goto done;
+		} else {
+			applied_share_acl = True;
+		}
+	}
+
+	if (us_vers == 2) {
+		if (strncmp(lines[4], "guest_ok=", 9) != 0) {
+			ret = USERSHARE_MALFORMED_ACL_DEF;
+			goto done;
+		}
+		if (lines[4][9] == 'y' && lp_usershare_allow_guests()) {
+			guest_ok = True;
+		}
+	}
+
+	if (snum != GLOBAL_SECTION_SNUM) {
+		lp_do_parameter(snum, "guest ok", guest_ok ? "yes" : "no");
+	}
+
+	ret = USERSHARE_OK;
+
+done:
+	/* Make sure we don't remove any existing share ACL just because we
+	 * found a bogus usershare file.
+	 */
+	if (applied_share_acl && ret != USERSHARE_OK) {
+		delete_share_security(snum2params_static(snum));
+	}
+
+	return ret;
+}
+
+/* Handle version 3. */
+static enum usershare_err parse_usershare_file_vers3(TALLOC_CTX *ctx,
+			const SMB_STRUCT_STAT *svcstat,
+			const char *servicename,
+			int snum,
+			int us_vers,
+			const char **lines,
+			int numlines)
+{
+	fstring param;
+	fstring value;
+
+	BOOL applied_share_acl = False;
+	enum usershare_err ret = USERSHARE_MALFORMED_FILE;
+	int i;
+
+	if (us_vers != 3) {
+		return USERSHARE_BAD_VERSION;
+	}
+
+	if (!lp_usershare_allow_full_config()) {
+		return USERSHARE_BAD_VERSION;
+	}
+
+	if (snum != GLOBAL_SECTION_SNUM) {
+		lp_do_parameter(snum, "guest ok", "no");
+	}
+
+	for (i = 1; i < numlines; ++i) {
+		const char *sep = strchr(lines[i], '=');
+		if (sep == NULL || /* no separator */
+		    sep == lines[i] || /* no param */
+		    *(sep + 1) == '\0') /* no value */ {
+			ret = USERSHARE_MALFORMED_FILE;
+			goto done;
+		}
+
+		/* More than 20 options is just silly - someone is messing with
+		 * us if this is the case.
+		 */
+		if (i > 20) {
+			ret = USERSHARE_MALFORMED_FILE;
+			goto done;
+		}
+
+		/* Make sure that both the param and the value will fit in an
+		 * fstring. Note that we don't support comments in ths file
+		 * format, so no need to trim them.
+		 */
+		if (PTR_DIFF(sep, lines[i]) >= sizeof(fstring) ||
+		    strlen(sep) >= sizeof(fstring)) {
+			ret = USERSHARE_MALFORMED_FILE;
+			goto done;
+		}
+
+		strncpy(param, lines[i], PTR_DIFF(sep, lines[i]));
+		param[PTR_DIFF(sep, lines[i])] = '\0';
+		trim_string(param, " ", " ");
+		trim_char(param, '"', '"');
+
+		strncpy(value, sep + 1, sizeof(fstring));
+		value[sizeof(fstring) - 1] = '\0';
+		trim_string(value, " ", " ");
+		trim_char(value, '"', '"');
+
+		if (strcmp(param, "path") == 0) {
+			ret = usershare_validate_path(snum, servicename,
+						svcstat, value);
+			if (ret != USERSHARE_OK) {
+				goto done;
+			}
+		} else if (strcmp(param, "guest ok") == 0) {
+			if (!lp_usershare_allow_guests()) {
+				continue;
+			}
+		} else if (strcmp(param, "usershare_acl") == 0) {
+			/* There's not standard config option for the
+			 * usershare share ACL syntax, so we roll it by hand.
+			 */
+			SEC_DESC *psd = NULL;
+
+			if (!parse_usershare_acl(ctx, value, &psd)) {
+				ret = USERSHARE_ACL_ERR;
+				goto done;
+			}
+
+			if (snum == GLOBAL_SECTION_SNUM) {
+				continue;
+			}
+
+			if (!set_share_security(servicename, psd)) {
+				 DEBUG(0, ("Failed to set share "
+					"security for user share %s\n",
+					servicename ));
+				ret = USERSHARE_ACL_ERR;
+				goto done;
+			}
+
+			applied_share_acl = True;
+			continue;
+		}
+
+		if (snum == GLOBAL_SECTION_SNUM) {
+			continue;
+		}
+
+		if (!lp_do_parameter(snum, param, value)) {
+			DEBUG(0, ("Malformed parameter '%s' "
+				"on user share %s\n", param, servicename));
+			ret = USERSHARE_MALFORMED_FILE;
+			goto done;
+		}
+	}
+
+	ret = USERSHARE_OK;
+
+done:
+	/* Make sure we don't remove any existing share ACL just because we
+	 * found a bogus usershare file.
+	 */
+	if (applied_share_acl && ret != USERSHARE_OK) {
+		delete_share_security(snum2params_static(snum));
+	}
+
+	return ret;
+}
+
+/***************************************************************************
+ Parse the contents of a usershare file. Passing GLOBAL_SECTION_SNUM as the
+ service number means to to as much parsing and validation as possible without
+ actually applying the configuration changes.
+***************************************************************************/
+
+enum usershare_err parse_usershare_file(TALLOC_CTX *ctx,
+			const SMB_STRUCT_STAT *svcstat,
+			const char *servicename,
+			int snum,
+			const char **lines,
+			int numlines)
+{
+	int us_vers = -1;
+
+	if (strcmp(lines[0], "#VERSION 1") == 0) {
+		us_vers = 1;
+	} else if (strcmp(lines[0], "#VERSION 2") == 0) {
+		us_vers = 2;
+	} else if (strcmp(lines[0], "#VERSION 3") == 0) {
+		/* Version 3 adds the ability to specify arbitrary smb.conf
+		 * parameters in a usershare file. We only allow this if
+		 * permission is granted by the admin.
+		 */
+		us_vers = 3;
+	}
+
+	switch (us_vers) {
+	case 1:
+	case 2:
+		return parse_usershare_file_vers2(ctx, svcstat, servicename,
+				snum, us_vers, lines, numlines);
+	case 3:
+		return parse_usershare_file_vers3(ctx, svcstat, servicename,
+				snum, us_vers, lines, numlines);
+	default:
+		return USERSHARE_BAD_VERSION;
+	}
+
+}
+
 /***************************************************************************
  Deal with a usershare file.
  Returns:
@@ -4548,21 +4780,20 @@ enum usershare_err parse_usershare_file(
 	    with permissions to share directory etc.
 ***************************************************************************/
 
-static int process_usershare_file(const char *dir_name, const char *file_name, int snum_template)
+static int process_usershare_file(const char *dir_name,
+			    const char *file_name,
+			    int snum_template)
 {
 	SMB_STRUCT_STAT sbuf;
 	SMB_STRUCT_STAT lsbuf;
 	pstring fname;
-	pstring sharepath;
-	pstring comment;
 	fstring service_name;
 	char **lines = NULL;
 	int numlines = 0;
 	int fd = -1;
 	int iService = -1;
 	TALLOC_CTX *ctx = NULL;
-	SEC_DESC *psd = NULL;
-	BOOL guest_ok = False;
+	service *saved_service = NULL;
 
 	/* Ensure share name doesn't contain invalid characters. */
 	if (!validate_net_name(file_name, INVALID_SHARENAME_CHARS, strlen(file_name))) {
@@ -4658,16 +4889,6 @@ static int process_usershare_file(const 
 		return 1;
 	}
 
-	if (parse_usershare_file(ctx, &sbuf, service_name,
-			iService, lines, numlines, sharepath,
-			comment, &psd, &guest_ok) != USERSHARE_OK) {
-		talloc_destroy(ctx);
-		file_lines_free(lines);
-		return -1;
-	}
-
-	file_lines_free(lines);
-
 	/* Everything ok - add the service possibly using a template. */
 	if (iService < 0) {
 		const service *sp = &sDefault;
@@ -4678,24 +4899,50 @@ static int process_usershare_file(const 
 		if ((iService = add_a_service(sp, service_name)) < 0) {
 			DEBUG(0, ("process_usershare_file: Failed to add "
 				"new service %s\n", service_name));
+			file_lines_free(lines);
 			talloc_destroy(ctx);
 			return -1;
 		}
 
+		/* Don't let this be valid until we have parsed the usershare
+		 * file correctly.
+		 */
+		ServicePtrs[iService]->valid = False;
+
 		/* Read only is controlled by usershare ACL below. */
 		ServicePtrs[iService]->bRead_only = False;
+	} else if (ServicePtrs[iService]->usershare == 0) {
+		/* We are modifying an existing service this is not a
+		 * usershare. This is OK, but we do not want a corrupt or
+		 * malicious usershare file to damage it. We save a copy of the
+		 * service so that we can restore it if necessary.
+		 */
+		saved_service = talloc_zero(ctx, service);
+		if (saved_service == NULL) {
+			file_lines_free(lines);
+			talloc_destroy(ctx);
+			return -1;
+		}
+
+		copy_service(saved_service, ServicePtrs[iService], NULL);
 	}
 
-	/* Write the ACL of the new/modified share. */
-	if (!set_share_security(service_name, psd)) {
-		 DEBUG(0, ("process_usershare_file: Failed to set share "
-			"security for user share %s\n",
-			service_name ));
-		lp_remove_service(iService);
+	if (parse_usershare_file(ctx, &sbuf, service_name, iService,
+			(const char **)lines, numlines) != USERSHARE_OK) {
+		if (saved_service) {
+			free_service(ServicePtrs[iService]);
+			copy_service(ServicePtrs[iService],
+					saved_service, NULL);
+			free_service(saved_service);
+		} else {
+			lp_remove_service(iService);
+		}
 		talloc_destroy(ctx);
+		file_lines_free(lines);
 		return -1;
 	}
 
+	file_lines_free(lines);
 	talloc_destroy(ctx);
 
 	/* If from a template it may be marked invalid. */
@@ -4704,15 +4951,8 @@ static int process_usershare_file(const 
 	/* Set the service as a valid usershare. */
 	ServicePtrs[iService]->usershare = USERSHARE_VALID;
 
-	/* Set guest access. */
-	if (lp_usershare_allow_guests()) {
-		ServicePtrs[iService]->bGuest_ok = guest_ok;
-	}
-
 	/* And note when it was loaded. */
 	ServicePtrs[iService]->usershare_last_mod = sbuf.st_mtime;
-	string_set(&ServicePtrs[iService]->szPath, sharepath);
-	string_set(&ServicePtrs[iService]->comment, comment);
 
 	return iService;
 }
Index: samba/source/utils/net_usershare.c
===================================================================
--- samba/source/utils/net_usershare.c.orig
+++ samba/source/utils/net_usershare.c
@@ -3,6 +3,7 @@
    Distributed SMB/CIFS Server Management Utility 
 
    Copyright (C) Jeremy Allison (jra@samba.org) 2005
+   Copyright (C) 2007 Apple Inc. All rights reserved.
 
    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
@@ -289,6 +290,79 @@ static int process_share_list(int (*fn)(
  Info function.
 ***************************************************************************/
 
+/* Parse the usershare_ok line from a version 1 - 3 usershare file and return a
+ * string containing the resolved ACL.
+ */
+static char * resolve_usershare_acl(void * ctx, const char * usershare_line)
+{
+	char *acl_str;
+	const char *acl_sep;
+	fstring acl_value;
+
+	int num_aces;
+	char sep_str[2];
+	SEC_DESC *psd = NULL;
+
+	sep_str[0] = *lp_winbind_separator();
+	sep_str[1] = '\0';
+
+	acl_sep = strchr(usershare_line, '=');
+	if (acl_sep == NULL || /* no separator */
+	    acl_sep == usershare_line || /* no param */
+	    *(acl_sep + 1) == '\0') /* no value */ {
+		/* Shouldn't happen because we already checked the syntax. */
+		DEBUG(0, ("invalid usershare_acl syntax: '%s'\n",
+			    usershare_line));
+		return NULL;
+	}
+
+	strncpy(acl_value, acl_sep + 1, sizeof(fstring));
+	trim_string(acl_value, " ", " ");
+	trim_char(acl_value, '"', '"');
+
+	if (!parse_usershare_acl(ctx, acl_value, &psd)) {
+		/* Shouldn't happen because we already checked the syntax. */
+		DEBUG(0, ("invalid usershare_acl: %s\n", acl_value));
+		return NULL;
+	}
+
+	acl_str = talloc_strdup(ctx, "");
+
+	for (num_aces = 0; num_aces < psd->dacl->num_aces; num_aces++) {
+		const char *domain;
+		const char *name;
+		NTSTATUS ntstatus;
+
+		ntstatus = net_lookup_name_from_sid(ctx, &psd->dacl->aces[num_aces].trustee, &domain, &name);
+
+		if (NT_STATUS_IS_OK(ntstatus)) {
+			if (domain && *domain) {
+				acl_str = talloc_asprintf_append(acl_str,
+					"%s%s", domain, sep_str);
+			}
+			acl_str = talloc_asprintf_append(acl_str, "%s", name);
+		} else {
+			fstring sidstr;
+			sid_to_string(sidstr, &psd->dacl->aces[num_aces].trustee);
+			acl_str = talloc_asprintf_append(acl_str, "%s", sidstr);
+		}
+
+		acl_str = talloc_asprintf_append(acl_str, ":");
+
+		if (psd->dacl->aces[num_aces].type == SEC_ACE_TYPE_ACCESS_DENIED) {
+			acl_str = talloc_asprintf_append(acl_str, "D");
+		} else {
+			if (psd->dacl->aces[num_aces].access_mask & GENERIC_ALL_ACCESS) {
+				acl_str = talloc_asprintf_append(acl_str, "F");
+			} else {
+				acl_str = talloc_asprintf_append(acl_str, "R");
+			}
+		}
+	}
+
+	return acl_str;
+}
+
 static int info_fn(struct file_list *fl, void *priv)
 {
 	SMB_STRUCT_STAT sbuf;
@@ -297,18 +371,8 @@ static int info_fn(struct file_list *fl,
 	TALLOC_CTX *ctx = pi->ctx;
 	int fd = -1;
 	int numlines = 0;
-	SEC_DESC *psd = NULL;
 	pstring basepath;
-	pstring sharepath;
-	pstring comment;
-	pstring acl_str;
-	int num_aces;
-	char sep_str[2];
 	enum usershare_err us_err;
-	BOOL guest_ok = False;
-
-	sep_str[0] = *lp_winbind_separator();
-	sep_str[1] = '\0';
 
 	get_basepath(basepath);
 	pstrcat(basepath, "/");
@@ -349,67 +413,45 @@ static int info_fn(struct file_list *fl,
 	}
 
 	/* Ensure it's well formed. */
-	us_err = parse_usershare_file(ctx, &sbuf, fl->pathname, -1, lines, numlines,
-				sharepath,
-				comment,
-				&psd,
-				&guest_ok);
-
-	file_lines_free(lines);
+	us_err = parse_usershare_file(ctx, &sbuf,
+			fl->pathname, GLOBAL_SECTION_SNUM,
+			(const char **)lines, numlines);
 
 	if (us_err != USERSHARE_OK) {
 		d_fprintf(stderr, "info_fn: file %s is not a well formed usershare file.\n",
 			basepath );
 		d_fprintf(stderr, "info_fn: Error was %s.\n",
 			get_us_error_code(us_err) );
+		file_lines_free(lines);
 		return -1;
 	}
 
-	pstrcpy(acl_str, "usershare_acl=");
-
-	for (num_aces = 0; num_aces < psd->dacl->num_aces; num_aces++) {
-		const char *domain;
-		const char *name;
-		NTSTATUS ntstatus;
-
-		ntstatus = net_lookup_name_from_sid(ctx, &psd->dacl->aces[num_aces].trustee, &domain, &name);
+	if (pi->op == US_LIST_OP) {
+		d_printf("%s\n", fl->pathname);
+	} else if (pi->op == US_INFO_OP) {
+		int i;
+		d_printf("[%s]\n", fl->pathname );
 
-		if (NT_STATUS_IS_OK(ntstatus)) {
-			if (domain && *domain) {
-				pstrcat(acl_str, domain);
-				pstrcat(acl_str, sep_str);
+		for (i = 0; i < numlines; ++i) {
+			if (*lines[i] == '#') {
+				continue;
 			}
-			pstrcat(acl_str,name);
-		} else {
-			fstring sidstr;
-			sid_to_string(sidstr, &psd->dacl->aces[num_aces].trustee);
-			pstrcat(acl_str,sidstr);
-		}
-		pstrcat(acl_str, ":");
 
-		if (psd->dacl->aces[num_aces].type == SEC_ACE_TYPE_ACCESS_DENIED) {
-			pstrcat(acl_str, "D,");
-		} else {
-			if (psd->dacl->aces[num_aces].access_mask & GENERIC_ALL_ACCESS) {
-				pstrcat(acl_str, "F,");
+			if (strncmp(lines[i], "usershare_acl", 13) == 0) {
+				char *acl_str;
+
+				acl_str = resolve_usershare_acl(ctx, lines[i]);
+				if (acl_str) {
+					d_printf("usershare_acl=%s\n",
+						acl_str);
+				}
 			} else {
-				pstrcat(acl_str, "R,");
+			    d_printf("%s\n", lines[i]);
 			}
 		}
 	}
 
-	acl_str[strlen(acl_str)-1] = '\0';
-
-	if (pi->op == US_INFO_OP) {
-		d_printf("[%s]\n", fl->pathname );
-		d_printf("path=%s\n", sharepath );
-		d_printf("comment=%s\n", comment);
-		d_printf("%s\n", acl_str);
-		d_printf("guest_ok=%c\n\n", guest_ok ? 'y' : 'n');
-	} else if (pi->op == US_LIST_OP) {
-		d_printf("%s\n", fl->pathname);
-	}
-
+	file_lines_free(lines);
 	return 0;
 }
 
@@ -545,6 +587,144 @@ static BOOL usershare_name_is_valid(cons
 	return True;
 }
 
+
+/* Go through and validate the ACL string. Convert names to SID's as
+ * needed. Then run it through parse_usershare_acl to ensure it's valid.
+ */
+static char * usershare_parse_acl_from_arg(void * ctx, const char *arg_acl)
+{
+	char *us_acl;
+	const char *pacl;
+	int num_aces;
+	int i;
+	SEC_DESC *psd = NULL;
+
+	/* Start off the string we'll append to. */
+	us_acl = talloc_strdup(ctx, "");
+
+	pacl = arg_acl;
+	num_aces = 1;
+
+	/* Add the number of ',' characters to get the number of aces. */
+	num_aces += count_chars(pacl,',');
+
+	for (i = 0; i < num_aces; i++) {
+		DOM_SID sid;
+		const char *pcolon = strchr_m(pacl, ':');
+		const char *name;
+
+		if (pcolon == NULL) {
+			d_fprintf(stderr, "net usershare add: malformed acl %s (missing ':').\n",
+				pacl );
+			return NULL;
+		}
+
+		switch(pcolon[1]) {
+			case 'f':
+			case 'F':
+			case 'd':
+			case 'r':
+			case 'R':
+				break;
+			default:
+				d_fprintf(stderr, "net usershare add: malformed acl %s "
+					"(access control must be 'r', 'f', or 'd')\n",
+					pacl );
+				return NULL;
+		}
+
+		if (pcolon[2] != ',' && pcolon[2] != '\0') {
+			d_fprintf(stderr, "net usershare add: malformed terminating character for acl %s\n",
+				pacl );
+			return NULL;
+		}
+
+		/* Get the name */
+		if ((name = talloc_strndup(ctx, pacl, pcolon - pacl)) == NULL) {
+			d_fprintf(stderr,
+			"net usershare add: memory allocation failure\n");
+			return NULL;
+		}
+		if (!string_to_sid(&sid, name)) {
+			/* Convert to a SID */
+			NTSTATUS ntstatus = net_lookup_sid_from_name(ctx, name, &sid);
+			if (!NT_STATUS_IS_OK(ntstatus)) {
+				d_fprintf(stderr, "net usershare add: cannot convert name \"%s\" to a SID. %s.",
+					name, get_friendly_nt_error_msg(ntstatus) );
+				if (NT_STATUS_EQUAL(ntstatus, NT_STATUS_CONNECTION_REFUSED)) {
+					d_fprintf(stderr,  " Maybe smbd is not running.\n");
+				} else {
+					d_fprintf(stderr, "\n");
+				}
+
+				return NULL;
+			}
+		}
+		us_acl = talloc_asprintf_append(us_acl, "%s:%c,", sid_string_static(&sid), pcolon[1]);
+
+		/* Move to the next ACL entry. */
+		if (pcolon[2] == ',') {
+			pacl = &pcolon[3];
+		}
+	}
+
+	/* Remove the last ',' */
+	us_acl[strlen(us_acl)-1] = '\0';
+
+	if (!parse_usershare_acl(ctx, us_acl, &psd)) {
+		d_fprintf(stderr,
+			"net usershare add: malformed share acl %s\n", us_acl);
+		return NULL;
+	}
+
+	return us_acl;
+}
+
+static char * usershare_mkfile_vers2(void *ctx,
+			const char * us_path,
+			const char * us_comment,
+			const char * us_acl,
+			BOOL guest_ok)
+{
+	char * file_img;
+
+	file_img = talloc_strdup(ctx, "#VERSION 2\npath=");
+	file_img = talloc_asprintf_append(file_img,
+			"%s\ncomment=%s\nusershare_acl=%s\nguest_ok=%c\n",
+			us_path, us_comment, us_acl, guest_ok ? 'y' : 'n');
+
+	return file_img;
+}
+
+static char * usershare_mkfile_vers3(void *ctx,
+			const char * us_path,
+			const char * us_comment,
+			const char * us_acl,
+			BOOL guest_ok,
+			const char ** options,
+			int num_options)
+{
+	int i;
+	char *file_img;
+
+	/* More than 20 options is just silly. */
+	if (num_options > 20) {
+		return NULL;
+	}
+
+	file_img = talloc_strdup(ctx, "#VERSION 3\n");
+	file_img = talloc_asprintf_append(file_img,
+			"path=%s\ncomment=%s\nusershare_acl=%s\nguest ok=%s\n",
+			us_path, us_comment, us_acl,
+			guest_ok ? "yes" : "no");
+
+	for (i = 0; i < num_options; ++i) {
+		file_img = talloc_asprintf_append(file_img, "%s\n", options[i]);
+	}
+
+	return file_img;
+}
+
 static int net_usershare_add(int argc, const char **argv)
 {
 	TALLOC_CTX *ctx = NULL;
@@ -558,14 +738,12 @@ static int net_usershare_add(int argc, c
 	const char *arg_acl;
 	char *us_acl;
 	char *file_img;
-	int num_aces = 0;
-	int i;
 	int tmpfd;
-	const char *pacl;
 	size_t to_write;
 	uid_t myeuid = geteuid();
 	BOOL guest_ok = False;
 	int num_usershares;
+	BOOL full_config = False;
 
 	us_comment = "";
 	arg_acl = "S-1-1-0:R";
@@ -573,7 +751,6 @@ static int net_usershare_add(int argc, c
 	switch (argc) {
 		case 0:
 		case 1:
-		default:
 			return net_usershare_add_usage(argc, argv);
 		case 2:
 			sharename = strdup_lower(argv[0]);
@@ -590,6 +767,9 @@ static int net_usershare_add(int argc, c
 			us_comment = argv[2];
 			arg_acl = argv[3];
 			break;
+		default:
+			full_config = True;
+			/* FALLTHRU */
 		case 5:
 			sharename = strdup_lower(argv[0]);
 			us_path = argv[1];
@@ -614,6 +794,14 @@ static int net_usershare_add(int argc, c
 					return net_usershare_add_usage(argc, argv);
 			}
 			break;
+
+	}
+
+	if (full_config && !lp_usershare_allow_full_config()) {
+		d_fprintf(stderr, "net usershare add: full usershare control "
+		    "is administratively disabled\n");
+		SAFE_FREE(sharename);
+		return -1;
 	}
 
 	/* Ensure we're under the "usershare max shares" number. Advisory only. */
@@ -674,92 +862,16 @@ static int net_usershare_add(int argc, c
 		return -1;
 	}
 
-	/* No validation needed on comment. Now go through and validate the
-	   acl string. Convert names to SID's as needed. Then run it through
-	   parse_usershare_acl to ensure it's valid. */
+	/* No validation needed on comment. */
 
 	ctx = talloc_init("share_info");
-
-	/* Start off the string we'll append to. */
-	us_acl = talloc_strdup(ctx, "");
-
-	pacl = arg_acl;
-	num_aces = 1;
-
-	/* Add the number of ',' characters to get the number of aces. */
-	num_aces += count_chars(pacl,',');
-
-	for (i = 0; i < num_aces; i++) {
-		DOM_SID sid;
-		const char *pcolon = strchr_m(pacl, ':');
-		const char *name;
-
-		if (pcolon == NULL) {
-			d_fprintf(stderr, "net usershare add: malformed acl %s (missing ':').\n",
-				pacl );
-			talloc_destroy(ctx);
-			SAFE_FREE(sharename);
-			return -1;
-		}
-
-		switch(pcolon[1]) {
-			case 'f':
-			case 'F':
-			case 'd':
-			case 'r':
-			case 'R':
-				break;
-			default:
-				d_fprintf(stderr, "net usershare add: malformed acl %s "
-					"(access control must be 'r', 'f', or 'd')\n",
-					pacl );
-				talloc_destroy(ctx);
-				SAFE_FREE(sharename);
-				return -1;
-		}
-
-		if (pcolon[2] != ',' && pcolon[2] != '\0') {
-			d_fprintf(stderr, "net usershare add: malformed terminating character for acl %s\n",
-				pacl );
-			talloc_destroy(ctx);
-			SAFE_FREE(sharename);
-			return -1;
-		}
-
-		/* Get the name */
-		if ((name = talloc_strndup(ctx, pacl, pcolon - pacl)) == NULL) {
-			d_fprintf(stderr, "talloc_strndup failed\n");
-			talloc_destroy(ctx);
-			SAFE_FREE(sharename);
-			return -1;
-		}
-		if (!string_to_sid(&sid, name)) {
-			/* Convert to a SID */
-			NTSTATUS ntstatus = net_lookup_sid_from_name(ctx, name, &sid);
-			if (!NT_STATUS_IS_OK(ntstatus)) {
-				d_fprintf(stderr, "net usershare add: cannot convert name \"%s\" to a SID. %s.",
-					name, get_friendly_nt_error_msg(ntstatus) );
-				if (NT_STATUS_EQUAL(ntstatus, NT_STATUS_CONNECTION_REFUSED)) {
-					d_fprintf(stderr,  " Maybe smbd is not running.\n");
-				} else {
-					d_fprintf(stderr, "\n");
-				}
-				talloc_destroy(ctx);
-				SAFE_FREE(sharename);
-				return -1;
-			}
-		}
-		us_acl = talloc_asprintf_append(us_acl, "%s:%c,", sid_string_static(&sid), pcolon[1]);
-
-		/* Move to the next ACL entry. */
-		if (pcolon[2] == ',') {
-			pacl = &pcolon[3];
-		}
+	us_acl = usershare_parse_acl_from_arg(ctx, arg_acl);
+	if (us_acl == NULL) {
+		talloc_destroy(ctx);
+		SAFE_FREE(sharename);
+		return -1;
 	}
 
-	/* Remove the last ',' */
-	us_acl[strlen(us_acl)-1] = '\0';
-
 	if (guest_ok && !lp_usershare_allow_guests()) {
 		d_fprintf(stderr, "net usershare add: guest_ok=y requested "
 			"but the \"usershare allow guests\" parameter is not enabled "
@@ -815,9 +927,14 @@ static int net_usershare_add(int argc, c
 	}
 
 	/* Create the in-memory image of the file. */
-	file_img = talloc_strdup(ctx, "#VERSION 2\npath=");
-	file_img = talloc_asprintf_append(file_img, "%s\ncomment=%s\nusershare_acl=%s\nguest_ok=%c\n",
-			us_path, us_comment, us_acl, guest_ok ? 'y' : 'n');
+
+	if (full_config) {
+		file_img = usershare_mkfile_vers3(ctx, us_path, us_comment,
+				us_acl, guest_ok, &argv[5], argc - 5);
+	} else {
+		file_img = usershare_mkfile_vers2(ctx, us_path, us_comment,
+				us_acl, guest_ok);
+	}
 
 	to_write = strlen(file_img);