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@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@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...ch/082669.html
+ http://lists.samba.org/archive/samba...ch/034925.html
+ http://nvs.fromru.com/samba-3.0.4-no...min-nvs.tar.gz
+
+Paul Szabo - psz@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: