join domain without root

Paul Szabo psz at maths.usyd.edu.au
Mon Sep 13 22:11:04 GMT 2004


Dear Samba gurus,

I have a domain of (Win2k) client PCs controlled by a Samba PDC. Joining
client PCs to the domain requires a "root" account to be used. This seems
an onerous imposition, as it involves a security risk. The following patch
(against version 3.0.5) seems to solve the issue, allowing any account
marked "domain admins" in smb.conf to be used. Security is improved: the
account(s) used for joining new or re-imaged PCs to the domain have no real
power over the Samba server, no need to give out the root password.

Cheers,

Paul Szabo - psz at maths.usyd.edu.au  http://www.maths.usyd.edu.au:8000/u/psz/
School of Mathematics and Statistics  University of Sydney   2006  Australia


--- param/loadparm.c.orig	Wed Jul 21 02:28:01 2004
+++ param/loadparm.c	Wed Sep  8 07:40:59 2004
@@ -181,6 +181,7 @@
 	char *szChangeShareCommand;
 	char *szDeleteShareCommand;
 	char *szGuestaccount;
+	char **szDomainAdmins;
 	char *szManglingMethod;
 	int mangle_prefix;
 	int max_log_size;
@@ -801,6 +802,7 @@
 	{"root dir", P_STRING, P_GLOBAL, &Globals.szRootdir, NULL, NULL, FLAG_HIDE}, 
 	{"root", P_STRING, P_GLOBAL, &Globals.szRootdir, NULL, NULL, FLAG_HIDE}, 
 	{"guest account", P_STRING, P_GLOBAL, &Globals.szGuestaccount, NULL, NULL, FLAG_BASIC | FLAG_ADVANCED}, 
+	{"domain admins", P_LIST, P_GLOBAL, &Globals.szDomainAdmins, NULL, NULL, FLAG_ADVANCED | FLAG_WIZARD}, 
 
 	{"pam password change", P_BOOL, P_GLOBAL, &Globals.bPamPasswordChange, NULL, NULL, FLAG_ADVANCED}, 
 	{"passwd program", P_STRING, P_GLOBAL, &Globals.szPasswdProgram, NULL, NULL, FLAG_ADVANCED}, 
@@ -1315,6 +1317,8 @@
 
 	string_set(&Globals.szGuestaccount, GUEST_ACCOUNT);
 
+	Globals.szDomainAdmins = NULL;
+
 	/* using UTF8 by default allows us to support all chars */
 	string_set(&Globals.unix_charset, DEFAULT_UNIX_CHARSET);
 
@@ -1448,7 +1452,8 @@
 
 	/* hostname lookups can be very expensive and are broken on
 	   a large number of sites (tridge) */
-	Globals.bHostnameLookups = False;
+	/* PSz  8 Sep 04 */ /* BUG */ /* Docs say default is Yes ... */
+	Globals.bHostnameLookups = True;
 
 #ifdef WITH_LDAP_SAMCONFIG
 	string_set(&Globals.szLdapServer, "localhost");
@@ -1662,6 +1667,7 @@
 FN_GLOBAL_STRING(lp_deluser_script, &Globals.szDelUserScript)
 
 FN_GLOBAL_CONST_STRING(lp_guestaccount, &Globals.szGuestaccount)
+FN_GLOBAL_LIST(lp_domain_admins, &Globals.szDomainAdmins)
 FN_GLOBAL_STRING(lp_addgroup_script, &Globals.szAddGroupScript)
 FN_GLOBAL_STRING(lp_delgroup_script, &Globals.szDelGroupScript)
 FN_GLOBAL_STRING(lp_addusertogroup_script, &Globals.szAddUserToGroupScript)
--- passdb/pdb_ldap.c.orig	Wed Jul 21 02:28:09 2004
+++ passdb/pdb_ldap.c	Wed Sep  8 07:36:04 2004
@@ -1731,6 +1731,15 @@
 		return ret;
 	}
 
+	/* nvs at fromru.com
+	   let LDAP to replicate changes to slaves.
+	   such code already is in lib/smbldap.c, but it based on
+	   ldap redirection to master server. I have slave server which
+	   updates master server by self, and samba is not redirected.
+	   Therefore I added sleep here. */
+	DEBUG(2,("ldapsam_add_sam_account: sleeping %ims to let LDAP to replicate data to slaves\n",lp_ldap_replication_sleep()));
+	smb_msleep(lp_ldap_replication_sleep());
+
 	DEBUG(2,("ldapsam_add_sam_account: added: uid == %s in the LDAP database\n", pdb_get_username(newpwd)));
 	ldap_mods_free(mods, True);
 	
--- rpc_server/srv_samr_nt.c.orig	Wed Jul 21 02:28:09 2004
+++ rpc_server/srv_samr_nt.c	Thu Sep  9 13:00:34 2004
@@ -70,6 +70,116 @@
 
 static NTSTATUS samr_make_dom_obj_sd(TALLOC_CTX *ctx, SEC_DESC **psd, size_t *sd_size);
 
+/* PSz  7 Sep 04 */
+/*
+Allow join-domain-without-root: allow joining the domain, without having
+to give away the root password.
+
+Allow things for any "domain admin", when a machine is joining the domain:
+right-clicking MyComputer Properties NetworkIdentification Properties, or
+with netdom or via sysprep/mini-setup.
+
+See also:
+
+  http://lists.samba.org/archive/samba/2004-March/082669.html
+  http://lists.samba.org/archive/samba-technical/2004-March/034925.html
+  http://nvs.fromru.com/samba-3.0.4-nonroot-domainadmin-nvs.tar.gz
+
+Paul Szabo - psz at maths.usyd.edu.au  http://www.maths.usyd.edu.au:8000/u/psz/
+School of Mathematics and Statistics  University of Sydney   2006  Australia
+ */
+
+static int PSz_own_WSTRUST = 0;
+
+/*******************************************************************
+ Check that SID is the client's own "machine trust account".
+********************************************************************/
+int PSz_is_own_WSTRUST(DOM_SID *sid)
+{
+	SAM_ACCOUNT *pwd = NULL;
+	uint16 acct_ctrl;
+	int need_root = 0;
+	BOOL ret;
+	char *sidname, *username, *hostname, *s;
+	int len;
+	int status = 0;
+
+	sidname = (char*)sid_string_static(sid);
+	if (!strcmp(sidname,"S-0-0")) {
+DEBUG(2, ("PSz: sid:%s might be my own WSTRUST\n", sidname));
+		return 1;
+	}
+	for (s = sidname, len = 0; *s && (s = index(s,'-')); s++, len++);
+	if (len < 7) {
+DEBUG(2, ("PSz: sid:%s has %d dashes only (surely not my own)\n", sidname, len));
+		return 0;
+	}
+ 	pdb_init_sam(&pwd);
+	while (1) {	/* So we can quit with break */
+		if (geteuid()) { need_root = 1; }
+		if (need_root) { become_root(); }
+	 	ret = pdb_getsampwsid(pwd, sid);
+		if (need_root) { unbecome_root(); }
+	 	if (!ret) {
+DEBUG(2, ("PSz: cannot get pwd for sid:%s\n", sidname));
+			break;
+	 	}
+		acct_ctrl = pdb_get_acct_ctrl(pwd);
+		username = (char*)pdb_get_username(pwd);
+		if ( (acct_ctrl &  ACB_WSTRUST) !=  ACB_WSTRUST) {
+DEBUG(2, ("PSz: sid:%s is user:%s, not WSTRUST\n", sidname, username));
+			break;
+		}
+		hostname = client_name();
+/* PSz  7 Sep 04 */ /* BUG */
+/*
+ * client_name() above depends on "hostname lookups = yes" being
+ * set in smb.conf: the default setting is "no" (despite docs).
+ */
+		/* Not simply len = strlen(hostname): stop at first dot */
+		for (s = hostname, len = 0; *s && *s != '.'; s++, len++);
+		if (! (
+		    len > 0 &&
+		    len + 1 == strlen(username) &&
+		    username[len] == '$' &&
+		    strncmp(hostname,username,len) == 0
+		    ) ) {
+DEBUG(0, ("PSz: sid:%s is user:%s, not my own (%s)\n", sidname, username, hostname));
+			break;
+		}
+DEBUG(0, ("PSz: sid:%s is user:%s, my own (%s) WSTRUST\n", sidname, username, hostname));
+		status = 1;
+		break;
+	}
+	pdb_free_sam(&pwd);
+
+	return status;
+}
+
+/*******************************************************************
+ Check that client is a "Domain Admin".
+********************************************************************/
+int PSz_is_domainadmin(void)
+{
+	char *username;
+	int status = 0;
+
+	username = (char*)uidtoname(geteuid());
+
+/*
+ * Any better way of checking for Domain Admin: enumerate all groups
+ * and check each? With the "easy domain admins" patch, use that:
+ * simpler and more immediate control. (Check group mappings buried
+ * in some TDB file with "net groupmap"? Oh-so-very-Windows.)
+ */
+	if ( user_in_list(username,(const char **)lp_domain_admins(),NULL,0) ) {
+		status = 1;
+DEBUG(2, ("PSz: %s is a domain admin\n", username));
+	}
+	
+	return status;
+}
+
 /*******************************************************************
  Checks if access to an object should be granted, and returns that
  level of access for further checks.
@@ -88,7 +198,20 @@
 			DEBUGADD(4,("but overritten by euid == sec_initial_uid()\n"));
 			status = NT_STATUS_OK;
 		}
+/* PSz  7 Sep 04 */
+/* Checking  (PSz_own_WSTRUST && PSz_is_domainadmin()) should be enough... */
+		else if ( ( PSz_own_WSTRUST ) &&
+			  ( ( des_access == 0x0211 &&
+			     !strcmp(debug,"_samr_open_domain") ) ||
+			    ( des_access == 0x00b0 &&
+			     !strcmp(debug,"_samr_open_user") ) ) &&
+			  ( PSz_is_domainadmin() ) ) {
+DEBUG(0,("PSz: access_check_samr_object: %s ALLOWED (requested %#010x) for %s\n", debug, des_access, uidtoname(geteuid())));
+			status = NT_STATUS_OK;
+		}
 		else {
+DEBUG(2,("PSz: access_check_samr_object: %s DENIED (requested %#010x) for %s\n", debug, des_access, uidtoname(geteuid())));
+
 			DEBUG(2,("%s: ACCESS DENIED  (requested: %#010x)\n",
 				debug, des_access));
 		}
@@ -111,6 +234,20 @@
 			DEBUGADD(4,("but overwritten by euid == 0\n"));
 			return NT_STATUS_OK;
 		}
+/* PSz  7 Sep 04 */
+/* Checking  (PSz_own_WSTRUST && PSz_is_domainadmin()) should be enough... */
+		else if ( ( PSz_own_WSTRUST ) &&
+			  ( ( acc_granted == 0x0201 &&
+			      acc_required == 0x0010 &&
+			     !strcmp(debug,"_samr_create_user") ) ||
+			    ( acc_granted == 0x00b0 &&
+			      acc_required == 0x0024 &&
+			     !strcmp(debug,"_samr_set_userinfo") ) ) &&
+			  ( PSz_is_domainadmin() ) ) {
+DEBUG(0,("PSz: access_check_samr_function: %s ALLOWED (granted %#010x required %#010x) for %s\n", debug, acc_granted, acc_required, uidtoname(geteuid())));
+			return NT_STATUS_OK;
+		}
+DEBUG(2,("PSz: access_check_samr_function: %s DENIED (granted %#010x required %#010x) for %s\n", debug, acc_granted, acc_required, uidtoname(geteuid())));
 		DEBUG(2,("%s: ACCESS DENIED (granted: %#010x;  required: %#010x)\n",
 			debug, acc_granted, acc_required));
 		return NT_STATUS_ACCESS_DENIED;
@@ -394,11 +531,15 @@
 	samr_make_dom_obj_sd(p->mem_ctx, &psd, &sd_size);
 	se_map_generic(&des_access,&dom_generic_mapping);
 
+/* PSz  7 Sep 04 */
+	PSz_own_WSTRUST = PSz_is_own_WSTRUST(&(info->sid));
 	if (!NT_STATUS_IS_OK(status = 
 			     access_check_samr_object(psd, p->pipe_user.nt_user_token, 
 						      des_access, &acc_granted, "_samr_open_domain"))) {
+		PSz_own_WSTRUST = 0;
 		return status;
 	}
+	PSz_own_WSTRUST = 0;
 
 	/* associate the domain SID with the (unique) handle. */
 	if ((info = get_samr_info_by_sid(&q_u->dom_sid.sid))==NULL)
@@ -1634,11 +1775,15 @@
 	/* check if access can be granted as requested by client. */
 	samr_make_usr_obj_sd(p->mem_ctx, &psd, &sd_size, &sid);
 	se_map_generic(&des_access, &usr_generic_mapping);
+/* PSz  7 Sep 04 */
+	PSz_own_WSTRUST = PSz_is_own_WSTRUST(&sid);
 	if (!NT_STATUS_IS_OK(nt_status = 
 			     access_check_samr_object(psd, p->pipe_user.nt_user_token, 
 						      des_access, &acc_granted, "_samr_open_user"))) {
+		PSz_own_WSTRUST = 0;
 		return nt_status;
 	}
+	PSz_own_WSTRUST = 0;
 
 	become_root();
 	ret=pdb_getsampwsid(sampass, &sid);
@@ -2147,9 +2292,13 @@
 	if (!get_lsa_policy_samr_sid(p, &dom_pol, &sid, &acc_granted))
 		return NT_STATUS_INVALID_HANDLE;
 
+/* PSz  7 Sep 04 */
+	PSz_own_WSTRUST = PSz_is_own_WSTRUST(&sid);
 	if (!NT_STATUS_IS_OK(nt_status = access_check_samr_function(acc_granted, SA_RIGHT_DOMAIN_CREATE_USER, "_samr_create_user"))) {
+		PSz_own_WSTRUST = 0;
 		return nt_status;
 	}
+	PSz_own_WSTRUST = 0;
 
 	if (!(acb_info == ACB_NORMAL || acb_info == ACB_DOMTRUST || acb_info == ACB_WSTRUST || acb_info == ACB_SVRTRUST)) { 
 		/* Match Win2k, and return NT_STATUS_INVALID_PARAMETER if 
@@ -2233,15 +2382,25 @@
 		 * normal that hidden accounts) with the acb_info equals to ACB_NORMAL.
 		 * JFM, 11/29/2001
 		 */
+/* PSz  8 Sep 04 */
+/*
+ * I do not use add machine script (but pre-create machines).
+ * Should be run as root, if ever used by a domain admin; but
+ * see warnings above, relax security for machine script only.
+ */
+		int need_root = 0;
 		if (account[strlen(account)-1] == '$')
 			pstrcpy(add_script, lp_addmachine_script());		
+			if (geteuid()) { need_root = 1; }
 		else 
 			pstrcpy(add_script, lp_adduser_script());
 
 		if (*add_script) {
   			int add_ret;
   			all_string_sub(add_script, "%u", account, sizeof(account));
+			if (need_root) { become_root(); }
   			add_ret = smbrun(add_script,NULL);
+			if (need_root) { unbecome_root(); }
  			DEBUG(3,("_samr_create_user: Running the command `%s' gave %d\n", add_script, add_ret));
   		}
 		else	/* no add user script -- ask winbindd to do it */
@@ -2969,10 +3128,17 @@
 	if (!get_lsa_policy_samr_sid(p, pol, &sid, &acc_granted))
 		return NT_STATUS_INVALID_HANDLE;
 	
+/* PSz  7 Sep 04 */
+DEBUG(2, ("PSz: _samr_set_userinfo: sid:%s, level:%d\n", sid_string_static(&sid), switch_value));
+	if (switch_value == 24) {
+		PSz_own_WSTRUST = PSz_is_own_WSTRUST(&sid);
+	}
 	acc_required = SA_RIGHT_USER_SET_LOC_COM | SA_RIGHT_USER_SET_ATTRIBUTES; /* This is probably wrong */	
 	if (!NT_STATUS_IS_OK(r_u->status = access_check_samr_function(acc_granted, acc_required, "_samr_set_userinfo"))) {
+		PSz_own_WSTRUST = 0;
 		return r_u->status;
 	}
+	PSz_own_WSTRUST = 0;
 		
 	DEBUG(5, ("_samr_set_userinfo: sid:%s, level:%d\n", sid_string_static(&sid), switch_value));
 
@@ -2996,8 +3162,22 @@
 
 			dump_data(100, (char *)ctr->info.id24->pass, 516);
 
-			if (!set_user_info_pw((char *)ctr->info.id24->pass, &sid))
+/* PSz  7 Sep 04 */
+/*
+ * Do as root (bracket within become_root()/unbecome_root() if it is
+ * a domain admin updating his own machine password, as checked above.
+ * (Otherwise the pdb_ calls fail for non-root.)
+ */
+{
+	int need_root = 0;
+	BOOL ret;
+	if (geteuid()) { need_root = 1; }
+	if (need_root) { become_root(); }
+			ret = set_user_info_pw((char *)ctr->info.id24->pass, &sid);
+	if (need_root) { unbecome_root(); }
+			if (!ret)
 				return NT_STATUS_ACCESS_DENIED;
+}
 			break;
 
 		case 25:


More information about the samba-technical mailing list