diff --git a/SOURCES/0001-Add-setattr-option.patch b/SOURCES/0001-Add-setattr-option.patch
new file mode 100644
index 0000000000000000000000000000000000000000..abc40bb102a83eb64e067887a730ddbf39d53682
--- /dev/null
+++ b/SOURCES/0001-Add-setattr-option.patch
@@ -0,0 +1,384 @@
+From c5b0cee2976682b4fc1aeb02636cc9f2c6dbc2a5 Mon Sep 17 00:00:00 2001
+From: Sumit Bose <sbose@redhat.com>
+Date: Mon, 14 Jun 2021 07:54:01 +0200
+Subject: [PATCH 1/2] Add setattr option
+
+With the new option common LDAP attributes can be set.
+
+Resolves: https://bugzilla.redhat.com/show_bug.cgi?id=1690920
+---
+ doc/adcli.xml      |  34 +++++++++
+ library/adenroll.c | 169 ++++++++++++++++++++++++++++++++++++++++++++-
+ library/adenroll.h |   4 ++
+ tools/computer.c   |  10 +++
+ 4 files changed, 216 insertions(+), 1 deletion(-)
+
+diff --git a/doc/adcli.xml b/doc/adcli.xml
+index 6c36297..8383aa7 100644
+--- a/doc/adcli.xml
++++ b/doc/adcli.xml
+@@ -374,6 +374,23 @@ Password for Administrator:
+ 			service should be accessible with a different host
+ 			name as well.</para></listitem>
+ 		</varlistentry>
++		<varlistentry>
++			<term><option>--setattr=<parameter>name=value</parameter></option></term>
++			<listitem><para>Add the LDAP attribute
++			<option><parameter>name</parameter></option> with the
++			given <option><parameter>value</parameter></option> to
++			the new LDAP host object.
++			This option can be used multiple times to add multiple
++			different attributes. Multi-value attributes are
++			currently not supported.</para>
++			<para>Please note that the account used to join the
++			domain must have the required privileges to add the
++			given attributes. Some attributes might have
++			constraints with respect to syntax and allowed values
++			which must be met as well. Attributes managed by other
++			adcli options cannot be set with this option.</para>
++			</listitem>
++		</varlistentry>
+ 		<varlistentry>
+ 			<term><option>--show-details</option></term>
+ 			<listitem><para>After a successful join print out information
+@@ -543,6 +560,23 @@ $ adcli update --login-ccache=/tmp/krbcc_123
+ 			<listitem><para>Remove a service principal name from
+ 			the keytab and the AD host object.</para></listitem>
+ 		</varlistentry>
++		<varlistentry>
++			<term><option>--setattr=<parameter>name=value</parameter></option></term>
++			<listitem><para>Add the LDAP attribute
++			<option><parameter>name</parameter></option> with the
++			given <option><parameter>value</parameter></option> to
++			the LDAP host object.
++			This option can be used multiple times to add multiple
++			different attributes. Multi-value attributes are
++			currently not supported.</para>
++			<para>Please note that the account used to update the
++			host object must have the required privileges to modify
++			the given attributes. Some attributes might have
++			constraints with respect to syntax and allowed values
++			which must be met as well. Attributes managed by other
++			adcli options cannot be set with this option.</para>
++			</listitem>
++		</varlistentry>
+ 		<varlistentry>
+ 			<term><option>--show-details</option></term>
+ 			<listitem><para>After a successful join print out information
+diff --git a/library/adenroll.c b/library/adenroll.c
+index 0b1c066..dd51567 100644
+--- a/library/adenroll.c
++++ b/library/adenroll.c
+@@ -150,4 +150,5 @@ struct _adcli_enroll {
+ 	char *description;
++	char **setattr;
+ };
+ 
+ static const char *
+@@ -795,6 +796,56 @@ calculate_enctypes (adcli_enroll *enroll, char **enctype)
+ 	return ADCLI_SUCCESS;
+ }
+ 
++static LDAPMod **
++get_mods_for_attrs (adcli_enroll *enroll, int mod_op)
++{
++	size_t len;
++	size_t c;
++	char *end;
++	LDAPMod **mods = NULL;
++
++	len = _adcli_strv_len (enroll->setattr);
++	if (len == 0) {
++		return NULL;
++	}
++
++	mods = calloc (len + 1, sizeof (LDAPMod *));
++	return_val_if_fail (mods != NULL, NULL);
++
++	for (c = 0; c < len; c++) {
++		end = strchr (enroll->setattr[c], '=');
++		if (end == NULL) {
++			ldap_mods_free (mods, 1);
++			return NULL;
++		}
++
++		mods[c] = calloc (1, sizeof (LDAPMod));
++		if (mods[c] == NULL) {
++			ldap_mods_free (mods, 1);
++			return NULL;
++		}
++
++		mods[c]->mod_op = mod_op;
++		*end = '\0';
++		mods[c]->mod_type = strdup (enroll->setattr[c]);
++		*end = '=';
++		mods[c]->mod_values = calloc (2, sizeof (char *));
++		if (mods[c]->mod_type == NULL || mods[c]->mod_values == NULL) {
++			ldap_mods_free (mods, 1);
++			return NULL;
++		}
++
++		mods[c]->mod_values[0] = strdup (end + 1);
++		if (mods[c]->mod_values[0] == NULL) {
++			ldap_mods_free (mods, 1);
++			return NULL;
++		}
++	}
++
++	return mods;
++}
++
++
+ static adcli_result
+ create_computer_account (adcli_enroll *enroll,
+                          LDAP *ldap)
+@@ -828,6 +879,7 @@ create_computer_account (adcli_enroll *enroll,
+ 	size_t m;
+ 	uint32_t uac = UAC_WORKSTATION_TRUST_ACCOUNT | UAC_DONT_EXPIRE_PASSWORD ;
+ 	char *uac_str = NULL;
++	LDAPMod **extra_mods = NULL;
+ 
+ 	LDAPMod *all_mods[] = {
+ 		&objectClass,
+@@ -845,7 +897,7 @@ create_computer_account (adcli_enroll *enroll,
+ 	};
+ 
+ 	size_t mods_count = sizeof (all_mods) / sizeof (LDAPMod *);
+-	LDAPMod *mods[mods_count];
++	LDAPMod **mods;
+ 
+ 	if (adcli_enroll_get_trusted_for_delegation (enroll)) {
+ 		uac |= UAC_TRUSTED_FOR_DELEGATION;
+@@ -868,6 +920,17 @@ create_computer_account (adcli_enroll *enroll,
+ 	}
+ 	vals_supportedEncryptionTypes[0] = val;
+ 
++	if (enroll->setattr != NULL) {
++		extra_mods = get_mods_for_attrs (enroll, LDAP_MOD_ADD);
++		if (extra_mods == NULL) {
++			_adcli_err ("Failed to add setattr attributes, "
++			            "just using defaults");
++		}
++	}
++
++	mods = calloc (mods_count + seq_count (extra_mods) + 1, sizeof (LDAPMod *));
++	return_val_if_fail (mods != NULL, ADCLI_ERR_UNEXPECTED);
++
+ 	m = 0;
+ 	for (c = 0; c < mods_count - 1; c++) {
+ 		/* Skip empty LDAP sttributes */
+@@ -875,9 +938,15 @@ create_computer_account (adcli_enroll *enroll,
+ 			mods[m++] = all_mods[c];
+ 		}
+ 	}
++
++	for (c = 0; c < seq_count (extra_mods); c++) {
++		mods[m++] = extra_mods[c];
++	}
+ 	mods[m] = NULL;
+ 
+ 	ret = ldap_add_ext_s (ldap, enroll->computer_dn, mods, NULL, NULL);
++	ldap_mods_free (extra_mods, 1);
++	free (mods);
+ 	free (uac_str);
+ 	free (val);
+ 
+@@ -1698,6 +1767,14 @@ update_computer_account (adcli_enroll *enroll)
+ 		res |= update_computer_attribute (enroll, ldap, mods);
+ 	}
+ 
++	if (res == ADCLI_SUCCESS && enroll->setattr != NULL) {
++		LDAPMod **mods = get_mods_for_attrs (enroll, LDAP_MOD_REPLACE);
++		if (mods != NULL) {
++			res |= update_computer_attribute (enroll, ldap, mods);
++			ldap_mods_free (mods, 1);
++		}
++	}
++
+ 	if (res != 0)
+ 		_adcli_info ("Updated existing computer account: %s", enroll->computer_dn);
+ }
+@@ -2751,6 +2828,7 @@ enroll_free (adcli_enroll *enroll)
+ 	free (enroll->user_principal);
+ 	_adcli_strv_free (enroll->service_names);
+ 	_adcli_strv_free (enroll->service_principals);
++	_adcli_strv_free (enroll->setattr);
+ 	_adcli_password_free (enroll->computer_password);
+ 
+ 	adcli_enroll_set_keytab_name (enroll, NULL);
+@@ -3332,6 +3410,72 @@ adcli_enroll_add_service_principal_to_remove (adcli_enroll *enroll,
+ 	return_if_fail (enroll->service_principals_to_remove != NULL);
+ }
+ 
++static int comp_attr_name (const char *s1, const char *s2)
++{
++	size_t c = 0;
++
++	/* empty strings cannot contain an attribute name */
++	if (s1 == NULL || s2 == NULL || *s1 == '\0' || *s2 == '\0') {
++		return 1;
++	}
++
++	for (c = 0 ; s1[c] != '\0' && s2[c] != '\0'; c++) {
++		if (s1[c] == '=' && s2[c] == '=') {
++			return 0;
++		} else if (tolower (s1[c]) != tolower (s2[c])) {
++			return 1;
++		}
++	}
++
++	return 1;
++}
++
++adcli_result
++adcli_enroll_add_setattr (adcli_enroll *enroll, const char *value)
++{
++	char *delim;
++
++	return_val_if_fail (enroll != NULL, ADCLI_ERR_CONFIG);
++	return_val_if_fail (value != NULL, ADCLI_ERR_CONFIG);
++
++	delim = strchr (value, '=');
++	if (delim == NULL) {
++		_adcli_err ("Missing '=' in setattr option [%s]", value);
++		return ADCLI_ERR_CONFIG;
++	}
++
++	if (*(delim + 1) == '\0') {
++		_adcli_err ("Missing value in setattr option [%s]", value);
++		return ADCLI_ERR_CONFIG;
++	}
++
++	*delim = '\0';
++	if (_adcli_strv_has_ex (default_ad_ldap_attrs, value, strcasecmp) == 1) {
++		_adcli_err ("Attribute [%s] cannot be set with setattr", value);
++		return ADCLI_ERR_CONFIG;
++	}
++	*delim = '=';
++
++	if (_adcli_strv_has_ex (enroll->setattr, value, comp_attr_name) == 1) {
++		_adcli_err ("Attribute [%s] already set", value);
++		return ADCLI_ERR_CONFIG;
++	}
++
++	enroll->setattr = _adcli_strv_add (enroll->setattr, strdup (value),
++	                                   NULL);
++	return_val_if_fail (enroll->setattr != NULL, ADCLI_ERR_CONFIG);
++
++	return ADCLI_SUCCESS;
++}
++
++const char **
++adcli_enroll_get_setattr (adcli_enroll *enroll)
++{
++	return_val_if_fail (enroll != NULL, NULL);
++	return (const char **) enroll->setattr;
++}
++
++
+ #ifdef ADENROLL_TESTS
+ 
+ #include "test.h"
+@@ -3401,12 +3545,35 @@ test_adcli_enroll_get_permitted_keytab_enctypes (void)
+ 	adcli_conn_unref (conn);
+ }
+ 
++static void
++test_comp_attr_name (void)
++{
++	assert_num_eq (1, comp_attr_name (NULL ,NULL));
++	assert_num_eq (1, comp_attr_name ("" ,NULL));
++	assert_num_eq (1, comp_attr_name ("" ,""));
++	assert_num_eq (1, comp_attr_name (NULL ,""));
++	assert_num_eq (1, comp_attr_name (NULL ,"abc=xyz"));
++	assert_num_eq (1, comp_attr_name ("" ,"abc=xyz"));
++	assert_num_eq (1, comp_attr_name ("abc=xyz", NULL));
++	assert_num_eq (1, comp_attr_name ("abc=xyz", ""));
++	assert_num_eq (1, comp_attr_name ("abc=xyz", "ab=xyz"));
++	assert_num_eq (1, comp_attr_name ("ab=xyz", "abc=xyz"));
++	assert_num_eq (1, comp_attr_name ("abcxyz", "abc=xyz"));
++	assert_num_eq (1, comp_attr_name ("abc=xyz", "abcxyz"));
++	assert_num_eq (1, comp_attr_name ("abc=xyz", "a"));
++	assert_num_eq (1, comp_attr_name ("a", "abc=xyz"));
++
++	assert_num_eq (0, comp_attr_name ("abc=xyz", "abc=xyz"));
++	assert_num_eq (0, comp_attr_name ("abc=xyz", "abc=123"));
++}
++
+ int
+ main (int argc,
+       char *argv[])
+ {
+ 	test_func (test_adcli_enroll_get_permitted_keytab_enctypes,
+ 	           "/attrs/adcli_enroll_get_permitted_keytab_enctypes");
++	test_func (test_comp_attr_name, "/attrs/comp_attr_name");
+ 	return test_run (argc, argv);
+ }
+ 
+diff --git a/library/adenroll.h b/library/adenroll.h
+index 34dc683..862bb60 100644
+--- a/library/adenroll.h
++++ b/library/adenroll.h
+@@ -138,6 +138,10 @@ const char *       adcli_enroll_get_desciption          (adcli_enroll *enroll);
+ void               adcli_enroll_set_description         (adcli_enroll *enroll,
+                                                          const char *value);
+ 
++const char **      adcli_enroll_get_setattr             (adcli_enroll *enroll);
++adcli_result       adcli_enroll_add_setattr             (adcli_enroll *enroll,
++                                                         const char *value);
++
+ bool               adcli_enroll_get_is_service          (adcli_enroll *enroll);
+ void               adcli_enroll_set_is_service          (adcli_enroll *enroll,
+                                                          bool value);
+diff --git a/tools/computer.c b/tools/computer.c
+index 16a1983..af38894 100644
+--- a/tools/computer.c
++++ b/tools/computer.c
+@@ -114,6 +114,7 @@ typedef enum {
+ 	opt_add_service_principal,
+ 	opt_remove_service_principal,
+ 	opt_description,
++	opt_setattr,
+ 	opt_use_ldaps,
+ } Option;
+ 
+@@ -152,6 +153,7 @@ static adcli_tool_desc common_usages[] = {
+ 	{ opt_add_service_principal, "add the given service principal to the account\n" },
+ 	{ opt_remove_service_principal, "remove the given service principal from the account\n" },
+ 	{ opt_description, "add a description to the account\n" },
++	{ opt_setattr, "add an attribute with a value\n" },
+ 	{ opt_no_password, "don't prompt for or read a password" },
+ 	{ opt_prompt_password, "prompt for a password if necessary" },
+ 	{ opt_stdin_password, "read a password from stdin (until EOF) if\n"
+@@ -333,6 +335,12 @@ parse_option (Option opt,
+ 	case opt_description:
+ 		adcli_enroll_set_description (enroll, optarg);
+ 		return ADCLI_SUCCESS;
++	case opt_setattr:
++		ret =  adcli_enroll_add_setattr (enroll, optarg);
++		if (ret != ADCLI_SUCCESS) {
++			warnx ("parsing setattr option failed");
++		}
++		return ret;
+ 	case opt_use_ldaps:
+ 		adcli_conn_set_use_ldaps (conn, true);
+ 		return ADCLI_SUCCESS;
+@@ -401,6 +409,7 @@ adcli_tool_computer_join (adcli_conn *conn,
+ 		{ "os-version", required_argument, NULL, opt_os_version },
+ 		{ "os-service-pack", optional_argument, NULL, opt_os_service_pack },
+ 		{ "description", optional_argument, NULL, opt_description },
++		{ "setattr", required_argument, NULL, opt_setattr },
+ 		{ "user-principal", optional_argument, NULL, opt_user_principal },
+ 		{ "trusted-for-delegation", required_argument, NULL, opt_trusted_for_delegation },
+ 		{ "dont-expire-password", required_argument, NULL, opt_dont_expire_password },
+@@ -524,6 +533,7 @@ adcli_tool_computer_update (adcli_conn *conn,
+ 		{ "os-version", required_argument, NULL, opt_os_version },
+ 		{ "os-service-pack", optional_argument, NULL, opt_os_service_pack },
+ 		{ "description", optional_argument, NULL, opt_description },
++		{ "setattr", required_argument, NULL, opt_setattr },
+ 		{ "user-principal", optional_argument, NULL, opt_user_principal },
+ 		{ "computer-password-lifetime", optional_argument, NULL, opt_computer_password_lifetime },
+ 		{ "trusted-for-delegation", required_argument, NULL, opt_trusted_for_delegation },
+-- 
+2.31.1
+
diff --git a/SOURCES/0001-Fix-for-dont-expire-password-option-and-join.patch b/SOURCES/0001-Fix-for-dont-expire-password-option-and-join.patch
new file mode 100644
index 0000000000000000000000000000000000000000..9740f3194a178604db5c0c902b72f5cd0c8b7ed9
--- /dev/null
+++ b/SOURCES/0001-Fix-for-dont-expire-password-option-and-join.patch
@@ -0,0 +1,27 @@
+From 924465af7a4f37390bfdfdb4971e88421f52f3d9 Mon Sep 17 00:00:00 2001
+From: Sumit Bose <sbose@redhat.com>
+Date: Thu, 3 Jun 2021 15:03:20 +0200
+Subject: [PATCH] Fix for dont-expire-password option and join
+
+Resolves: https://bugzilla.redhat.com/show_bug.cgi?id=1769644
+---
+ library/adenroll.c | 3 ++-
+ 1 file changed, 2 insertions(+), 1 deletion(-)
+
+diff --git a/library/adenroll.c b/library/adenroll.c
+index f3d606e..5b0dcd5 100644
+--- a/library/adenroll.c
++++ b/library/adenroll.c
+@@ -856,7 +856,8 @@ create_computer_account (adcli_enroll *enroll,
+ 		uac |= UAC_TRUSTED_FOR_DELEGATION;
+ 	}
+ 
+-	if (!adcli_enroll_get_dont_expire_password (enroll)) {
++	if (enroll->dont_expire_password_explicit
++		       && !adcli_enroll_get_dont_expire_password (enroll)) {
+ 		uac &= ~(UAC_DONT_EXPIRE_PASSWORD);
+ 	}
+ 
+-- 
+2.31.1
+
diff --git a/SOURCES/0001-build-add-with-vendor-error-message-configure-option.patch b/SOURCES/0001-build-add-with-vendor-error-message-configure-option.patch
new file mode 100644
index 0000000000000000000000000000000000000000..75235eef192d6b17a769a7bb832089b5fd352d8a
--- /dev/null
+++ b/SOURCES/0001-build-add-with-vendor-error-message-configure-option.patch
@@ -0,0 +1,60 @@
+From 0353d704879f20983184f8bded4f16538d72f7cc Mon Sep 17 00:00:00 2001
+From: Sumit Bose <sbose@redhat.com>
+Date: Wed, 10 Mar 2021 18:12:09 +0100
+Subject: [PATCH] build: add --with-vendor-error-message configure option
+
+With the new configure option --with-vendor-error-message a packager or
+a distribution can add a message if adcli returns with an error.
+
+Resolves: https://bugzilla.redhat.com/show_bug.cgi?id=1889386
+---
+ configure.ac  | 15 +++++++++++++++
+ tools/tools.c |  6 ++++++
+ 2 files changed, 21 insertions(+)
+
+diff --git a/configure.ac b/configure.ac
+index baa0d3b..7dfba97 100644
+--- a/configure.ac
++++ b/configure.ac
+@@ -123,6 +123,21 @@ if test "$sasl_invalid" = "yes"; then
+ 	AC_MSG_ERROR([Couldn't find Cyrus SASL headers])
+ fi
+ 
++# --------------------------------------------------------------------
++# Vendor error message
++
++AC_ARG_WITH([vendor-error-message],
++              [AS_HELP_STRING([--with-vendor-error-message=ARG],
++                            [Add a vendor specific error message shown if a adcli command fails]
++                           )],
++              [AS_IF([test "x$withval" != "x"],
++                     [AC_DEFINE_UNQUOTED([VENDOR_MSG],
++                                         ["$withval"],
++                                         [Vendor specific error message])],
++                     [AC_MSG_ERROR([--with-vendor-error-message requires an argument])]
++                    )],
++              [])
++
+ # --------------------------------------------------------------------
+ # Documentation options
+ 
+diff --git a/tools/tools.c b/tools/tools.c
+index d0dcf98..84bbba9 100644
+--- a/tools/tools.c
++++ b/tools/tools.c
+@@ -538,6 +538,12 @@ main (int argc,
+ 
+ 		if (conn)
+ 			adcli_conn_unref (conn);
++#ifdef VENDOR_MSG
++		if (ret != 0) {
++			fprintf (stderr, VENDOR_MSG"\n");
++		}
++#endif
++
+ 		return ret;
+ 	}
+ 
+-- 
+2.30.2
+
diff --git a/SOURCES/0001-coverity-add-missing-NULL-checks.patch b/SOURCES/0001-coverity-add-missing-NULL-checks.patch
new file mode 100644
index 0000000000000000000000000000000000000000..2f419920a9348baa024d49e0fce12a5ee4d000f9
--- /dev/null
+++ b/SOURCES/0001-coverity-add-missing-NULL-checks.patch
@@ -0,0 +1,44 @@
+From 13fe79c0a78028ccfe8e3d4e5ee16cfb9e143924 Mon Sep 17 00:00:00 2001
+From: Sumit Bose <sbose@redhat.com>
+Date: Wed, 2 Jun 2021 13:39:31 +0200
+Subject: [PATCH 1/2] coverity: add missing NULL checks
+
+---
+ library/adenroll.c | 2 ++
+ library/adldap.c   | 7 +++++++
+ 2 files changed, 9 insertions(+)
+
+diff --git a/library/adenroll.c b/library/adenroll.c
+index f693e58..c726093 100644
+--- a/library/adenroll.c
++++ b/library/adenroll.c
+@@ -3046,6 +3046,8 @@ adcli_enroll_set_keytab_enctypes (adcli_enroll *enroll,
+ 	krb5_enctype *newval = NULL;
+ 	int len;
+ 
++	return_if_fail (enroll != NULL);
++
+ 	if (value) {
+ 		for (len = 0; value[len] != 0; len++);
+ 		newval = malloc (sizeof (krb5_enctype) * (len + 1));
+diff --git a/library/adldap.c b/library/adldap.c
+index d93efb7..b86014c 100644
+--- a/library/adldap.c
++++ b/library/adldap.c
+@@ -231,6 +231,13 @@ _adcli_ldap_have_in_mod (LDAPMod *mod,
+ 
+ 	vals = malloc (sizeof (struct berval) * (count + 1));
+ 	pvals = malloc (sizeof (struct berval *) * (count + 1));
++	if (vals == NULL || pvals == NULL) {
++		_adcli_err ("Memory allocation failed, assuming attribute must be updated.");
++		free (vals);
++		free (pvals);
++		return 0;
++	}
++
+ 	for (i = 0; i < count; i++) {
+ 		vals[i].bv_val = mod->mod_vals.modv_strvals[i];
+ 		vals[i].bv_len = strlen (vals[i].bv_val);
+-- 
+2.31.1
+
diff --git a/SOURCES/0001-doc-add-missing-samba_data_tool_path.xml-.in-to-EXTR.patch b/SOURCES/0001-doc-add-missing-samba_data_tool_path.xml-.in-to-EXTR.patch
new file mode 100644
index 0000000000000000000000000000000000000000..7fc04d3c7f718abe04988db319b7eab74f054b78
--- /dev/null
+++ b/SOURCES/0001-doc-add-missing-samba_data_tool_path.xml-.in-to-EXTR.patch
@@ -0,0 +1,25 @@
+From 2edc26afda17db1a92703deb16658e9de9f79e14 Mon Sep 17 00:00:00 2001
+From: Sumit Bose <sbose@redhat.com>
+Date: Tue, 3 Sep 2019 14:39:37 +0200
+Subject: [PATCH] doc: add missing samba_data_tool_path.xml(.in) to EXTRA_DIST
+
+---
+ doc/Makefile.am | 2 ++
+ 1 file changed, 2 insertions(+)
+
+diff --git a/doc/Makefile.am b/doc/Makefile.am
+index 3a53843..4490688 100644
+--- a/doc/Makefile.am
++++ b/doc/Makefile.am
+@@ -31,6 +31,8 @@ EXTRA_DIST = \
+ 	static \
+ 	version.xml.in \
+ 	version.xml \
++	samba_data_tool_path.xml.in \
++	samba_data_tool_path.xml \
+ 	$(NULL)
+ 
+ CLEANFILES = \
+-- 
+2.28.0
+
diff --git a/SOURCES/0001-doc-explain-required-AD-permissions.patch b/SOURCES/0001-doc-explain-required-AD-permissions.patch
new file mode 100644
index 0000000000000000000000000000000000000000..fe0826acd3f0598766a3ef34bfe309e98e322905
--- /dev/null
+++ b/SOURCES/0001-doc-explain-required-AD-permissions.patch
@@ -0,0 +1,242 @@
+From fa5c5fb4f8e7bcadf3e5a3798bd060720fd35eaa Mon Sep 17 00:00:00 2001
+From: Sumit Bose <sbose@redhat.com>
+Date: Tue, 20 Oct 2020 13:34:41 +0200
+Subject: [PATCH] doc: explain required AD permissions
+
+When using a restricted account with adcli some operations might fail
+because the account might not have all required permissions. The man
+page is extended and now explains which permissions are needed under
+given circumstances.
+
+Resolves: https://bugzilla.redhat.com/show_bug.cgi?id=1852080
+Resolves: https://gitlab.freedesktop.org/realmd/adcli/-/issues/20
+---
+ doc/Makefile.am    |  10 ++++
+ doc/adcli.xml      | 132 +++++++++++++++++++++++++++++++++++++++++++++
+ library/adenroll.c |  30 ++++++-----
+ 3 files changed, 160 insertions(+), 12 deletions(-)
+
+diff --git a/doc/Makefile.am b/doc/Makefile.am
+index 4490688..50fb777 100644
+--- a/doc/Makefile.am
++++ b/doc/Makefile.am
+@@ -33,14 +33,17 @@ EXTRA_DIST = \
+ 	version.xml \
+ 	samba_data_tool_path.xml.in \
+ 	samba_data_tool_path.xml \
++	permissions.xml \
+ 	$(NULL)
+ 
+ CLEANFILES = \
+ 	$(man8_MANS) \
++	permissions.xml \
+ 	$(NULL)
+ 
+ XSLTPROC_FLAGS = \
+ 	--nonet \
++	--xinclude \
+ 	--stringparam man.output.quietly 1 \
+ 	--stringparam funcsynopsis.style ansi \
+ 	--stringparam man.th.extra1.suppress 1 \
+@@ -50,6 +53,13 @@ XSLTPROC_FLAGS = \
+ XSLTPROC_MAN = \
+ 	$(XSLTPROC) $(XSLTPROC_FLAGS) http://docbook.sourceforge.net/release/xsl/current/manpages/docbook.xsl
+ 
++permissions.xml: ../library/adenroll.c adcli.xml
++	echo "<itemizedlist>" > $@
++	grep '".*".*/\* :ADPermissions: ' $< | sed -e 's#.*"\(.*\)".*/\* :ADPermissions: \(.*\)\*/$$#<listitem><para>\1</para><itemizedlist><listitem><para>\2</para></listitem></itemizedlist></listitem>#' | sed -e 's#\*#</para></listitem><listitem><para>#g' >> $@
++	echo "</itemizedlist>" >> $@
++
++$(man8_MANS): permissions.xml
++
+ .xml.8:
+ 	$(AM_V_GEN) $(XSLTPROC_MAN) $<
+ 
+diff --git a/doc/adcli.xml b/doc/adcli.xml
+index 1437679..cc44fd8 100644
+--- a/doc/adcli.xml
++++ b/doc/adcli.xml
+@@ -885,6 +885,138 @@ Password for Administrator:
+ 
+ </refsect1>
+ 
++<refsect1 id='delegation'>
++	<title>Delegated Permissions</title>
++	<para>It is common practice in AD to not use an account from the Domain
++	Administrators group to join a machine to a domain but use a dedicated
++	account which only has permissions to join a machine to one or more OUs
++	in the Active Directory tree. Giving the needed permissions to a single
++	account or a group in Active Directory is called Delegation. A typical
++	example on how to configured Delegation can be found in the Delegation
++	section of the blog post
++	<ulink url="https://docs.microsoft.com/en-us/archive/blogs/dubaisec/who-can-add-workstation-to-the-domain">Who can add workstation to the domain</ulink>.
++	</para>
++
++	<para>When using an account with delegated permissions with adcli
++	basically the same applies as well. However some aspects are explained
++	here in a bit more details to better illustrate different concepts of
++	Active Directory and to make it more easy to debug permissions issues
++	during the join. Please note that the following is not specific to
++	adcli but applies to all applications which would like to modify
++	certain properties or objects in Active Directory with an account with
++	limited permissions.</para>
++
++	<para>First, as said in the blog post it is sufficient to have
++	<literal>"Create computer object"</literal> permissions to join a
++	computer to a domain. But this would only work as expected if the
++	computer object does not exist in Active Directory before the join.
++	Because only when a new object is created Active Directory does not
++	apply additional permission checks on the attributes of the new
++	computer object. This means the delegated user can add any kind of
++	attribute with any value to a new computer object also long as they
++	meet general constraints like e.g. that the attribute must be defined
++	in the schema and is allowed in a objectclass of the object, the value
++	must match the syntax defined in the schema or that the
++	<option>sAMAccountName</option> must be unique in the domain.</para>
++
++	<para>If you want to use the account with delegated permission to
++	remove computer objects in Active Directory (adcli delete-computer) you
++	should of course make sure that the account has
++	<literal>"Delete computer object"</literal> permissions.</para>
++
++	<para>If the computer object already exists the
++	<literal>"Create computer object"</literal> permission does not apply
++	anymore since now an existing object must be modified. Now permissions
++	on the individual attributes are needed. e.g.
++	<literal>"Read and write Account Restrictions"</literal> or
++	<literal>"Reset Password"</literal>. For some attributes Active
++	Directory has two types of permissions the plain
++	<literal>"Read and Write"</literal> permissions and the
++	<literal>"Validated Write"</literal> permissions. For the latter case
++	there are two specific permissions relevant for adcli, namely
++		<itemizedlist>
++			<listitem><para>Validated write to DNS host name</para></listitem>
++			<listitem><para>Validated write to service principal name</para></listitem>
++		</itemizedlist>
++	Details about the validation of the values can be found in the
++	<literal>"Validated Writes"</literal> section of
++	<literal>[MS-ADTS]</literal>, especially
++	<ulink url="https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-adts/5c578b15-d619-408d-ba17-380714b89fd1">dNSHostName</ulink>
++	and
++	<ulink url="https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-adts/28ca4eca-0e0b-4666-9175-a37ccb8edada">servicePrincipalName</ulink>.
++	To cut it short for <literal>"Validated write to DNS host name"</literal>
++	the domain part of the fully-qualified hostname must either match the
++	domain name of the domain you want to join to or must be listed in the
++	<option>msDS-AllowedDNSSuffixes</option> attribute. And for
++	<literal>"Validated write to service principal name"</literal> the
++	hostname part of the service principal name must match the name stored
++	in <option>dNSHostName</option> or some other attributes which are
++	not handled by adcli. This also means that
++	<option>dNSHostName</option> cannot be empty or only contain a short
++	name if the service principal name should contain a fully-qualified
++	name.</para>
++
++	<para>To summarize, if you only have validated write permissions you
++	should make sure the domain part of the hostname matches the domain you
++	want to join or use the <option>--host-fqdn</option> with a matching
++	name.</para>
++
++	<para>The plain read write permissions do not run additional
++	validations but the attribute values must still be in agreement with
++	the general constraints mentioned above. If the computer object already
++	exists adcli might need the following permissions which are also needed
++	by Windows clients to modify existing attributes:
++		<itemizedlist>
++			<listitem><para>Reset Password</para></listitem>
++			<listitem><para>Read and write Account Restrictions</para></listitem>
++			<listitem><para>Read and (validated) write to DNS host name</para></listitem>
++			<listitem><para>Read and (validated) write to service principal name</para></listitem>
++		</itemizedlist>
++	additionally adcli needs
++		<itemizedlist>
++			<listitem><para>Read and write msDS-supportedEncryptionTypes</para></listitem>
++		</itemizedlist>
++	This is added for security reasons to avoid that Active Directory
++	stores Kerberos keys with (potentially weaker) encryption types than
++	the client supports since Active Directory is often configured to still
++	support older (weaker) encryption types for compatibility reasons.
++	</para>
++
++	<para>All other attributes are only set or modified on demand, i.e.
++	adcli must be called with an option the would set or modify the given
++	attribute. In the following the attributes adcli can modify together
++	with the required permissions are listed:
++	<xi:include xmlns:xi="http://www.w3.org/2001/XInclude" href="permissions.xml" />
++	</para>
++
++	<para>For the management of users and groups (adcli create-user,
++	adcli delete-user, adcli create-group, adcli delete-group) the same
++	applies only for different types of objects, i.e. users and groups.
++	Since currently adcli only supports the creation and the removal of
++	user and group objects it is sufficient to have the
++	<literal>"Create/Delete User objects"</literal> and
++	<literal>"Create/Delete Group objects"</literal> permissions.</para>
++
++	<para>If you want to manage group members as well (adcli add-member,
++	adcli remove-member) <literal>"Read/Write Members"</literal> permissions
++	are needed as well.</para>
++
++	<para>Depending on the version of Active Directory the
++	<literal>"Delegation of Control Wizard"</literal> might offer some
++	shortcuts for common task like e.g.
++		<itemizedlist>
++			<listitem><para>Create, delete and manage user accounts</para></listitem>
++			<listitem><para>Create, delete and manage groups</para></listitem>
++			<listitem><para>Modify the membership of a group</para></listitem>
++		</itemizedlist>
++	The first 2 shortcuts will provided full access to user and group
++	objects which, as explained above, is more than currently is needed.
++	After using those shortcut it is a good idea to verify in the
++	<literal>"Security"</literal> tab in the <literal>"Properties"</literal>
++	of the related Active Directory container that the assigned permissions
++	meet the expectations.</para>
++</refsect1>
++
+ <refsect1 id='bugs'>
+ 	<title>Bugs</title>
+ 	<para>
+diff --git a/library/adenroll.c b/library/adenroll.c
+index e745295..98e9786 100644
+--- a/library/adenroll.c
++++ b/library/adenroll.c
+@@ -71,19 +71,25 @@ static krb5_enctype v51_earlier_enctypes[] = {
+ 	0
+ };
+ 
++/* The following list containst all attributes handled by adcli, some are
++ * read-only and the others can be written as well. To properly document the
++ * required permissions each attribute which adcli tries to modify should have
++ * a comment starting with ':ADPermissions:' and the related permissions in AD
++ * on the same line. Multiple permissions can be seperated with a '*'. For all
++ * other attribute a suitable comment is very welcome. */
+ static char *default_ad_ldap_attrs[] =  {
+-	"sAMAccountName",
+-	"userPrincipalName",
+-	"msDS-KeyVersionNumber",
+-	"msDS-supportedEncryptionTypes",
+-	"dNSHostName",
+-	"servicePrincipalName",
+-	"operatingSystem",
+-	"operatingSystemVersion",
+-	"operatingSystemServicePack",
+-	"pwdLastSet",
+-	"userAccountControl",
+-	"description",
++	"sAMAccountName", /* Only set during creation */
++	"userPrincipalName",   /* :ADPermissions: Read/Write userPrincipal Name */
++	"msDS-KeyVersionNumber", /* Manages by AD */
++	"msDS-supportedEncryptionTypes", /* :ADPermissions: Read/Write msDS-SupportedEncryptionTypes */
++	"dNSHostName", /* :ADPermissions: Read/Write dNSHostName * Read and write DNS host name attributes * Validated write to DNS host name */
++	"servicePrincipalName", /* :ADPermissions: Read/Write servicePrincipalName * Validated write to service principal name */
++	"operatingSystem", /* :ADPermissions: Read/Write Operating System */
++	"operatingSystemVersion", /* :ADPermissions: Read/Write Operating System Version */
++	"operatingSystemServicePack", /* :ADPermissions: Read/Write operatingSystemServicePack */
++	"pwdLastSet", /* Managed by AD */
++	"userAccountControl", /* :ADPermissions: Read/Write userAccountControl */
++	"description", /* :ADPermissions: Read/Write Description */
+ 	NULL,
+ };
+ 
+-- 
+2.28.0
+
diff --git a/SOURCES/0001-enroll-add-is_service-member.patch b/SOURCES/0001-enroll-add-is_service-member.patch
new file mode 100644
index 0000000000000000000000000000000000000000..0a5129a4993144da55cba6b6721c70a6e723e528
--- /dev/null
+++ b/SOURCES/0001-enroll-add-is_service-member.patch
@@ -0,0 +1,66 @@
+From 4e4dbf8d2b437808863f8be85e7f30865d88c7fc Mon Sep 17 00:00:00 2001
+From: Sumit Bose <sbose@redhat.com>
+Date: Fri, 23 Oct 2020 16:46:43 +0200
+Subject: [PATCH 1/7] enroll: add is_service member
+
+Add helpers to indicate a managed service account.
+
+Resolves: https://bugzilla.redhat.com/show_bug.cgi?id=1854112
+---
+ library/adenroll.c | 17 +++++++++++++++++
+ library/adenroll.h |  4 ++++
+ 2 files changed, 21 insertions(+)
+
+diff --git a/library/adenroll.c b/library/adenroll.c
+index 98e9786..5ae1f7b 100644
+--- a/library/adenroll.c
++++ b/library/adenroll.c
+@@ -103,6 +103,8 @@ static char *default_ad_ldap_attrs[] =  {
+ struct _adcli_enroll {
+ 	int refs;
+ 	adcli_conn *conn;
++	bool is_service;
++	bool is_service_explicit;
+ 
+ 	char *host_fqdn;
+ 	int host_fqdn_explicit;
+@@ -2942,6 +2944,21 @@ adcli_enroll_get_desciption (adcli_enroll *enroll)
+ 	return enroll->description;
+ }
+ 
++void
++adcli_enroll_set_is_service (adcli_enroll *enroll, bool value)
++{
++	return_if_fail (enroll != NULL);
++
++	enroll->is_service = value;
++	enroll->is_service_explicit = true;
++}
++
++bool
++adcli_enroll_get_is_service (adcli_enroll *enroll)
++{
++	return enroll->is_service;
++}
++
+ const char **
+ adcli_enroll_get_service_principals_to_add (adcli_enroll *enroll)
+ {
+diff --git a/library/adenroll.h b/library/adenroll.h
+index 0606169..7765ed4 100644
+--- a/library/adenroll.h
++++ b/library/adenroll.h
+@@ -130,6 +130,10 @@ const char *       adcli_enroll_get_desciption          (adcli_enroll *enroll);
+ void               adcli_enroll_set_description         (adcli_enroll *enroll,
+                                                          const char *value);
+ 
++bool               adcli_enroll_get_is_service          (adcli_enroll *enroll);
++void               adcli_enroll_set_is_service          (adcli_enroll *enroll,
++                                                         bool value);
++
+ krb5_kvno          adcli_enroll_get_kvno                (adcli_enroll *enroll);
+ 
+ void               adcli_enroll_set_kvno                (adcli_enroll *enroll,
+-- 
+2.28.0
+
diff --git a/SOURCES/0001-join-update-set-dNSHostName-if-not-set.patch b/SOURCES/0001-join-update-set-dNSHostName-if-not-set.patch
new file mode 100644
index 0000000000000000000000000000000000000000..a27653e50a72774fdaf35892a9167521e60b9979
--- /dev/null
+++ b/SOURCES/0001-join-update-set-dNSHostName-if-not-set.patch
@@ -0,0 +1,59 @@
+From beb7abfacc0010987d2cd8ab70f7c373d309eed9 Mon Sep 17 00:00:00 2001
+From: Sumit Bose <sbose@redhat.com>
+Date: Thu, 15 Oct 2020 18:01:12 +0200
+Subject: [PATCH] join/update: set dNSHostName if not set
+
+If during a join or update an existing AD computer object does not have
+the dNSHostName attribute set it will be set with the current hostname.
+This is important for cases where the user doing the join or update only
+has "Validated write to service principal name" for the computer object.
+The validated write with fully-qualified names can only be successful if
+dNSHostName is set, see [MS-ADTS] section 3.1.1.5.3.1.1.4 "Validated
+Writes - servicePrincipalName" for details.
+
+Resolves https://bugzilla.redhat.com/show_bug.cgi?id=1734764
+---
+ library/adenroll.c | 16 ++++++++++++----
+ 1 file changed, 12 insertions(+), 4 deletions(-)
+
+diff --git a/library/adenroll.c b/library/adenroll.c
+index 246f658..e745295 100644
+--- a/library/adenroll.c
++++ b/library/adenroll.c
+@@ -1403,21 +1403,29 @@ update_computer_account (adcli_enroll *enroll)
+ {
+ 	int res = 0;
+ 	LDAP *ldap;
++	char *value = NULL;
+ 
+ 	ldap = adcli_conn_get_ldap_connection (enroll->conn);
+ 	return_if_fail (ldap != NULL);
+ 
+ 	/* Only update attributes which are explicitly given on the command
+-	 * line. Otherwise 'adcli update' must be always called with the same
+-	 * set of options to make sure existing attributes are not deleted or
+-	 * overwritten with different values. */
+-	if (enroll->host_fqdn_explicit) {
++	 * line or not set in the existing AD object. Otherwise 'adcli update'
++	 * must be always called with the same set of options to make sure
++	 * existing attributes are not deleted or overwritten with different
++	 * values. */
++	if (enroll->computer_attributes != NULL) {
++		value = _adcli_ldap_parse_value (ldap,
++		                                 enroll->computer_attributes,
++		                                 "dNSHostName");
++	}
++	if (enroll->host_fqdn_explicit || value == NULL ) {
+ 		char *vals_dNSHostName[] = { enroll->host_fqdn, NULL };
+ 		LDAPMod dNSHostName = { LDAP_MOD_REPLACE, "dNSHostName", { vals_dNSHostName, } };
+ 		LDAPMod *mods[] = { &dNSHostName, NULL };
+ 
+ 		res |= update_computer_attribute (enroll, ldap, mods);
+ 	}
++	free (value);
+ 
+ 	if (res == ADCLI_SUCCESS && enroll->trusted_for_delegation_explicit) {
+ 		char *vals_userAccountControl[] = { NULL , NULL };
+-- 
+2.28.0
+
diff --git a/SOURCES/0001-library-move-UAC-flags-to-a-more-common-header-file.patch b/SOURCES/0001-library-move-UAC-flags-to-a-more-common-header-file.patch
new file mode 100644
index 0000000000000000000000000000000000000000..0129517fc4a01c2ce6fedcc0170840d0c0413396
--- /dev/null
+++ b/SOURCES/0001-library-move-UAC-flags-to-a-more-common-header-file.patch
@@ -0,0 +1,50 @@
+From a7a40ce4f47fe40305624b6d86c135b7d27c387d Mon Sep 17 00:00:00 2001
+From: Sumit Bose <sbose@redhat.com>
+Date: Fri, 11 Jun 2021 12:44:36 +0200
+Subject: [PATCH 1/3] library: move UAC flags to a more common header file
+
+---
+ library/adenroll.c  | 8 --------
+ library/adprivate.h | 8 ++++++++
+ 2 files changed, 8 insertions(+), 8 deletions(-)
+
+diff --git a/library/adenroll.c b/library/adenroll.c
+index f00d179..0b1c066 100644
+--- a/library/adenroll.c
++++ b/library/adenroll.c
+@@ -93,13 +93,6 @@ static char *default_ad_ldap_attrs[] =  {
+ 	NULL,
+ };
+ 
+-/* Some constants for the userAccountControl AD LDAP attribute, see e.g.
+- * https://support.microsoft.com/en-us/help/305144/how-to-use-the-useraccountcontrol-flags-to-manipulate-user-account-pro
+- * for details. */
+-#define UAC_WORKSTATION_TRUST_ACCOUNT  0x1000
+-#define UAC_DONT_EXPIRE_PASSWORD      0x10000
+-#define UAC_TRUSTED_FOR_DELEGATION    0x80000
+-
+ struct _adcli_enroll {
+ 	int refs;
+ 	adcli_conn *conn;
+diff --git a/library/adprivate.h b/library/adprivate.h
+index 55e6234..822f919 100644
+--- a/library/adprivate.h
++++ b/library/adprivate.h
+@@ -39,6 +39,14 @@
+ #define HOST_NAME_MAX 255
+ #endif
+ 
++/* Some constants for the userAccountControl AD LDAP attribute, see e.g.
++ * https://support.microsoft.com/en-us/help/305144/how-to-use-the-useraccountcontrol-flags-to-manipulate-user-account-pro
++ * for details. */
++#define UAC_ACCOUNTDISABLE             0x0002
++#define UAC_WORKSTATION_TRUST_ACCOUNT  0x1000
++#define UAC_DONT_EXPIRE_PASSWORD      0x10000
++#define UAC_TRUSTED_FOR_DELEGATION    0x80000
++
+ /* Utilities */
+ 
+ #if !defined(__cplusplus) && (__GNUC__ > 2)
+-- 
+2.31.1
+
diff --git a/SOURCES/0001-man-make-handling-of-optional-credential-cache-more-.patch b/SOURCES/0001-man-make-handling-of-optional-credential-cache-more-.patch
new file mode 100644
index 0000000000000000000000000000000000000000..3d5955a15b48ceba59ae6c112989c3f5003852bc
--- /dev/null
+++ b/SOURCES/0001-man-make-handling-of-optional-credential-cache-more-.patch
@@ -0,0 +1,41 @@
+From 88fbb7e2395dec20b37697a213a097909870c21f Mon Sep 17 00:00:00 2001
+From: Sumit Bose <sbose@redhat.com>
+Date: Thu, 13 Aug 2020 17:10:01 +0200
+Subject: [PATCH] man: make handling of optional credential cache more clear
+
+The optional Kerberos credential cache can only be used with the long
+option name --login-ccache and not with the short version -C. To make
+this more clear each option get its own entry.
+
+Resolves: https://bugzilla.redhat.com/show_bug.cgi?id=1791545
+---
+ doc/adcli.xml | 12 +++++++++---
+ 1 file changed, 9 insertions(+), 3 deletions(-)
+
+diff --git a/doc/adcli.xml b/doc/adcli.xml
+index ecf8726..1437679 100644
+--- a/doc/adcli.xml
++++ b/doc/adcli.xml
+@@ -153,10 +153,16 @@ $ LDAPTLS_CACERT=/path/to/ad_dc_ca_cert.pem adcli join --use-ldaps -D domain.exa
+ 			</para></listitem>
+ 		</varlistentry>
+ 		<varlistentry>
+-			<term><option>-C, --login-ccache=<parameter>ccache_name</parameter></option></term>
+-			<listitem><para>Use the specified kerberos credential
++			<term><option>-C</option></term>
++			<listitem><para>Use the default Kerberos credential
++			cache to authenticate with the domain.
++			</para></listitem>
++		</varlistentry>
++		<varlistentry>
++			<term><option>--login-ccache<parameter>[=ccache_name]</parameter></option></term>
++			<listitem><para>Use the specified Kerberos credential
+ 			cache to authenticate with the domain. If no credential
+-			cache is specified, the default kerberos credential
++			cache is specified, the default Kerberos credential
+ 			cache will be used. Credential caches of type FILE can
+ 			be given with the path to the file. For other
+ 			credential cache types, e.g. DIR, KEYRING or KCM, the
+-- 
+2.26.2
+
diff --git a/SOURCES/0001-service-account-fix-typo-in-the-man-page-entry.patch b/SOURCES/0001-service-account-fix-typo-in-the-man-page-entry.patch
new file mode 100644
index 0000000000000000000000000000000000000000..b476a93e41df01a3f623653aefab60f8f30aca96
--- /dev/null
+++ b/SOURCES/0001-service-account-fix-typo-in-the-man-page-entry.patch
@@ -0,0 +1,35 @@
+From 637cc53953ef61c90530ae5eaf26eb4911336465 Mon Sep 17 00:00:00 2001
+From: Sumit Bose <sbose@redhat.com>
+Date: Thu, 10 Dec 2020 18:29:18 +0100
+Subject: [PATCH] service-account: fix typo in the man page entry
+
+Resolves: https://bugzilla.redhat.com/show_bug.cgi?id=1906303
+---
+ doc/adcli.xml | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/doc/adcli.xml b/doc/adcli.xml
+index 14921f9..a64687a 100644
+--- a/doc/adcli.xml
++++ b/doc/adcli.xml
+@@ -943,7 +943,7 @@ Password for Administrator:
+ 	of the managed service account as a suffix. On most systems it would be
+ 	<filename>/etc/krb5.keytab</filename> with a suffix of
+ 	'domain.example.com', e.g.
+-	<filename>/etc/krb5.keytad.domain.example.com</filename>.</para>
++	<filename>/etc/krb5.keytab.domain.example.com</filename>.</para>
+ 
+ 	<para><command>adcli create-msa</command> can be called multiple
+ 	times to reset the password of the managed service account. To identify
+@@ -955,7 +955,7 @@ Password for Administrator:
+ 
+ 	<para>The managed service account password can be updated with
+ <programlisting>
+-$ adcli update --domain=domain.example.com --host-keytab=/etc/krb5.keytad.domain.example.com
++$ adcli update --domain=domain.example.com --host-keytab=/etc/krb5.keytab.domain.example.com
+ </programlisting>
+ 	and the managed service account can be deleted with
+ <programlisting>
+-- 
+2.29.2
+
diff --git a/SOURCES/0001-tools-add-missing-use-ldaps-option-to-update-and-tes.patch b/SOURCES/0001-tools-add-missing-use-ldaps-option-to-update-and-tes.patch
new file mode 100644
index 0000000000000000000000000000000000000000..a62fbe6edcb2513dc745ea40294285c77611ef6b
--- /dev/null
+++ b/SOURCES/0001-tools-add-missing-use-ldaps-option-to-update-and-tes.patch
@@ -0,0 +1,36 @@
+From 76ca1e6737742208d83e016d43a3379e378f8d90 Mon Sep 17 00:00:00 2001
+From: Sumit Bose <sbose@redhat.com>
+Date: Wed, 14 Oct 2020 17:44:10 +0200
+Subject: [PATCH] tools: add missing use-ldaps option to update and testjoin
+
+When adding the use-ldaps option the update and testjoin sub-commands
+were forgotten.
+
+Resolves: https://bugzilla.redhat.com/show_bug.cgi?id=1883467
+---
+ tools/computer.c | 2 ++
+ 1 file changed, 2 insertions(+)
+
+diff --git a/tools/computer.c b/tools/computer.c
+index 24ea258..5a97d8b 100644
+--- a/tools/computer.c
++++ b/tools/computer.c
+@@ -491,6 +491,7 @@ adcli_tool_computer_update (adcli_conn *conn,
+ 	struct option options[] = {
+ 		{ "domain", required_argument, NULL, opt_domain },
+ 		{ "domain-controller", required_argument, NULL, opt_domain_controller },
++		{ "use-ldaps", no_argument, 0, opt_use_ldaps },
+ 		{ "host-fqdn", required_argument, 0, opt_host_fqdn },
+ 		{ "computer-name", required_argument, 0, opt_computer_name },
+ 		{ "host-keytab", required_argument, 0, opt_host_keytab },
+@@ -612,6 +613,7 @@ adcli_tool_computer_testjoin (adcli_conn *conn,
+ 	struct option options[] = {
+ 		{ "domain", required_argument, NULL, opt_domain },
+ 		{ "domain-controller", required_argument, NULL, opt_domain_controller },
++		{ "use-ldaps", no_argument, 0, opt_use_ldaps },
+ 		{ "host-keytab", required_argument, 0, opt_host_keytab },
+ 		{ "verbose", no_argument, NULL, opt_verbose },
+ 		{ "help", no_argument, NULL, 'h' },
+-- 
+2.28.0
+
diff --git a/SOURCES/0002-Add-delattr-option.patch b/SOURCES/0002-Add-delattr-option.patch
new file mode 100644
index 0000000000000000000000000000000000000000..92cf6237f82e6fcdf05530472ca31ca7cec6f7bb
--- /dev/null
+++ b/SOURCES/0002-Add-delattr-option.patch
@@ -0,0 +1,191 @@
+From cd5b6cdcf3e6bfc5776f2865f460f608421dfa3f Mon Sep 17 00:00:00 2001
+From: Sumit Bose <sbose@redhat.com>
+Date: Mon, 14 Jun 2021 08:42:21 +0200
+Subject: [PATCH 2/2] Add delattr option
+
+Resolves: https://bugzilla.redhat.com/show_bug.cgi?id=1690920
+---
+ doc/adcli.xml      | 11 ++++++++
+ library/adenroll.c | 66 ++++++++++++++++++++++++++++++++++++++++++++++
+ library/adenroll.h |  4 +++
+ tools/computer.c   |  9 +++++++
+ 4 files changed, 90 insertions(+)
+
+diff --git a/doc/adcli.xml b/doc/adcli.xml
+index 8383aa7..bcf4857 100644
+--- a/doc/adcli.xml
++++ b/doc/adcli.xml
+@@ -577,6 +577,17 @@ $ adcli update --login-ccache=/tmp/krbcc_123
+ 			adcli options cannot be set with this option.</para>
+ 			</listitem>
+ 		</varlistentry>
++		<varlistentry>
++			<term><option>--delattr=<parameter>name</parameter></option></term>
++			<listitem><para>Remove the LDAP attribute
++			<option><parameter>name</parameter></option> from the
++			LDAP host object. This option can be used multiple
++			times to remove multiple different attributes.</para>
++			<para>Please note that the account used to update the
++			host object must have the required privileges to delete
++			the given attributes. Attributes managed by other adcli
++			options cannot be removed.</para></listitem>
++		</varlistentry>
+ 		<varlistentry>
+ 			<term><option>--show-details</option></term>
+ 			<listitem><para>After a successful join print out information
+diff --git a/library/adenroll.c b/library/adenroll.c
+index dd51567..9a06d52 100644
+--- a/library/adenroll.c
++++ b/library/adenroll.c
+@@ -151,5 +151,6 @@ struct _adcli_enroll {
+ 	char *description;
+ 	char **setattr;
++	char **delattr;
+ };
+ 
+ static const char *
+@@ -845,6 +846,39 @@ get_mods_for_attrs (adcli_enroll *enroll, int mod_op)
+ 	return mods;
+ }
+ 
++static LDAPMod **
++get_del_mods_for_attrs (adcli_enroll *enroll, int mod_op)
++{
++	size_t len;
++	size_t c;
++	LDAPMod **mods = NULL;
++
++	len = _adcli_strv_len (enroll->delattr);
++	if (len == 0) {
++		return NULL;
++	}
++
++	mods = calloc (len + 1, sizeof (LDAPMod *));
++	return_val_if_fail (mods != NULL, NULL);
++
++	for (c = 0; c < len; c++) {
++		mods[c] = calloc (1, sizeof (LDAPMod));
++		if (mods[c] == NULL) {
++			ldap_mods_free (mods, 1);
++			return NULL;
++		}
++
++		mods[c]->mod_op = mod_op;
++		mods[c]->mod_type = strdup (enroll->delattr[c]);
++		mods[c]->mod_values = NULL;
++		if (mods[c]->mod_type == NULL) {
++			ldap_mods_free (mods, 1);
++			return NULL;
++		}
++	}
++
++	return mods;
++}
+ 
+ static adcli_result
+ create_computer_account (adcli_enroll *enroll,
+@@ -1775,6 +1809,14 @@ update_computer_account (adcli_enroll *enroll)
+ 		}
+ 	}
+ 
++	if (res == ADCLI_SUCCESS && enroll->delattr != NULL) {
++		LDAPMod **mods = get_del_mods_for_attrs (enroll, LDAP_MOD_DELETE);
++		if (mods != NULL) {
++			res |= update_computer_attribute (enroll, ldap, mods);
++			ldap_mods_free (mods, 1);
++		}
++	}
++
+ 	if (res != 0)
+ 		_adcli_info ("Updated existing computer account: %s", enroll->computer_dn);
+ }
+@@ -3475,6 +3517,30 @@ adcli_enroll_get_setattr (adcli_enroll *enroll)
+ 	return (const char **) enroll->setattr;
+ }
+ 
++adcli_result
++adcli_enroll_add_delattr (adcli_enroll *enroll, const char *value)
++{
++	return_val_if_fail (enroll != NULL, ADCLI_ERR_CONFIG);
++	return_val_if_fail (value != NULL, ADCLI_ERR_CONFIG);
++
++	if (_adcli_strv_has_ex (default_ad_ldap_attrs, value, strcasecmp) == 1) {
++		_adcli_err ("Attribute [%s] cannot be removed with delattr", value);
++		return ADCLI_ERR_CONFIG;
++	}
++
++	enroll->delattr = _adcli_strv_add (enroll->delattr, strdup (value),
++	                                   NULL);
++	return_val_if_fail (enroll->delattr != NULL, ADCLI_ERR_CONFIG);
++
++	return ADCLI_SUCCESS;
++}
++
++const char **
++adcli_enroll_get_delattr (adcli_enroll *enroll)
++{
++	return_val_if_fail (enroll != NULL, NULL);
++	return (const char **) enroll->delattr;
++}
+ 
+ #ifdef ADENROLL_TESTS
+ 
+diff --git a/library/adenroll.h b/library/adenroll.h
+index 862bb60..e3ada33 100644
+--- a/library/adenroll.h
++++ b/library/adenroll.h
+@@ -142,6 +142,10 @@ const char **      adcli_enroll_get_setattr             (adcli_enroll *enroll);
+ adcli_result       adcli_enroll_add_setattr             (adcli_enroll *enroll,
+                                                          const char *value);
+ 
++const char **      adcli_enroll_get_delattr             (adcli_enroll *enroll);
++adcli_result       adcli_enroll_add_delattr             (adcli_enroll *enroll,
++                                                         const char *value);
++
+ bool               adcli_enroll_get_is_service          (adcli_enroll *enroll);
+ void               adcli_enroll_set_is_service          (adcli_enroll *enroll,
+                                                          bool value);
+diff --git a/tools/computer.c b/tools/computer.c
+index af38894..dffeecb 100644
+--- a/tools/computer.c
++++ b/tools/computer.c
+@@ -115,6 +115,7 @@ typedef enum {
+ 	opt_remove_service_principal,
+ 	opt_description,
+ 	opt_setattr,
++	opt_delattr,
+ 	opt_use_ldaps,
+ } Option;
+ 
+@@ -154,6 +155,7 @@ static adcli_tool_desc common_usages[] = {
+ 	{ opt_remove_service_principal, "remove the given service principal from the account\n" },
+ 	{ opt_description, "add a description to the account\n" },
+ 	{ opt_setattr, "add an attribute with a value\n" },
++	{ opt_delattr, "remove an attribute\n" },
+ 	{ opt_no_password, "don't prompt for or read a password" },
+ 	{ opt_prompt_password, "prompt for a password if necessary" },
+ 	{ opt_stdin_password, "read a password from stdin (until EOF) if\n"
+@@ -341,6 +343,12 @@ parse_option (Option opt,
+ 			warnx ("parsing setattr option failed");
+ 		}
+ 		return ret;
++	case opt_delattr:
++		ret = adcli_enroll_add_delattr (enroll, optarg);
++		if (ret != ADCLI_SUCCESS) {
++			warnx ("parsing delattr option failed");
++		}
++		return ret;
+ 	case opt_use_ldaps:
+ 		adcli_conn_set_use_ldaps (conn, true);
+ 		return ADCLI_SUCCESS;
+@@ -534,6 +542,7 @@ adcli_tool_computer_update (adcli_conn *conn,
+ 		{ "os-service-pack", optional_argument, NULL, opt_os_service_pack },
+ 		{ "description", optional_argument, NULL, opt_description },
+ 		{ "setattr", required_argument, NULL, opt_setattr },
++		{ "delattr", required_argument, NULL, opt_delattr },
+ 		{ "user-principal", optional_argument, NULL, opt_user_principal },
+ 		{ "computer-password-lifetime", optional_argument, NULL, opt_computer_password_lifetime },
+ 		{ "trusted-for-delegation", required_argument, NULL, opt_trusted_for_delegation },
+-- 
+2.31.1
+
diff --git a/SOURCES/0002-Add-dont-expire-password-option.patch b/SOURCES/0002-Add-dont-expire-password-option.patch
new file mode 100644
index 0000000000000000000000000000000000000000..0ae8a90b7ce28c6472c6113674aaa4bee36349db
--- /dev/null
+++ b/SOURCES/0002-Add-dont-expire-password-option.patch
@@ -0,0 +1,241 @@
+From 74b52a30c2b142118b7f26f78c014e7bee825e84 Mon Sep 17 00:00:00 2001
+From: Sumit Bose <sbose@redhat.com>
+Date: Wed, 2 Jun 2021 17:24:07 +0200
+Subject: [PATCH 2/2] Add dont-expire-password option
+
+Resolves: https://bugzilla.redhat.com/show_bug.cgi?id=1769644
+---
+ doc/adcli.xml      | 28 ++++++++++++++++++++++
+ library/adenroll.c | 58 +++++++++++++++++++++++++++++++++++++++++-----
+ library/adenroll.h |  4 ++++
+ tools/computer.c   | 12 ++++++++++
+ 4 files changed, 96 insertions(+), 6 deletions(-)
+
+diff --git a/doc/adcli.xml b/doc/adcli.xml
+index a64687a..7c2e126 100644
+--- a/doc/adcli.xml
++++ b/doc/adcli.xml
+@@ -347,6 +347,20 @@ Password for Administrator:
+ 			not allow that Kerberos tickets can be forwarded to the
+ 			host.</para></listitem>
+ 		</varlistentry>
++		<varlistentry>
++			<term><option>--dont-expire-password=<parameter>yes|no|true|false</parameter></option></term>
++			<listitem><para>Set or unset the DONT_EXPIRE_PASSWORD
++			flag in the userAccountControl attribute to indicate if
++			the machine account password should expire or not. By
++			default adcli will set this flag while joining the
++			domain which corresponds to the default behavior of
++			Windows clients.</para>
++			<para>Please note that if the password will expire
++			(--dont-expire-password=false) a renewal mechanism has
++			to be enabled on the client to not loose the
++			connectivity to AD if the password expires.</para>
++			</listitem>
++		</varlistentry>
+ 		<varlistentry>
+ 			<term><option>--add-service-principal=<parameter>service/hostname</parameter></option></term>
+ 			<listitem><para>Add a service principal name. In
+@@ -491,6 +505,20 @@ $ adcli update --login-ccache=/tmp/krbcc_123
+ 			not allow that Kerberos tickets can be forwarded to the
+ 			host.</para></listitem>
+ 		</varlistentry>
++		<varlistentry>
++			<term><option>--dont-expire-password=<parameter>yes|no|true|false</parameter></option></term>
++			<listitem><para>Set or unset the DONT_EXPIRE_PASSWORD
++			flag in the userAccountControl attribute to indicate if
++			the machine account password should expire or not. By
++			default adcli will set this flag while joining the
++			domain which corresponds to the default behavior of
++			Windows clients.</para>
++			<para>Please note that if the password will expire
++			(--dont-expire-password=false) a renewal mechanism has
++			to be enabled on the client to not loose the
++			connectivity to AD if the password expires.</para>
++			</listitem>
++		</varlistentry>
+ 		<varlistentry>
+ 			<term><option>--add-service-principal=<parameter>service/hostname</parameter></option></term>
+ 			<listitem><para>Add a service principal name. In
+diff --git a/library/adenroll.c b/library/adenroll.c
+index c726093..01f149c 100644
+--- a/library/adenroll.c
++++ b/library/adenroll.c
+@@ -152,6 +152,8 @@ struct _adcli_enroll {
+ 	char *samba_data_tool;
+ 	bool trusted_for_delegation;
+ 	int trusted_for_delegation_explicit;
++	bool dont_expire_password;
++	int dont_expire_password_explicit;
+ 	char *description;
+ };
+ 
+@@ -829,6 +831,8 @@ create_computer_account (adcli_enroll *enroll,
+ 	int ret;
+ 	size_t c;
+ 	size_t m;
++	uint32_t uac = UAC_WORKSTATION_TRUST_ACCOUNT | UAC_DONT_EXPIRE_PASSWORD ;
++	char *uac_str = NULL;
+ 
+ 	LDAPMod *all_mods[] = {
+ 		&objectClass,
+@@ -849,11 +853,21 @@ create_computer_account (adcli_enroll *enroll,
+ 	LDAPMod *mods[mods_count];
+ 
+ 	if (adcli_enroll_get_trusted_for_delegation (enroll)) {
+-		vals_userAccountControl[0] = "593920"; /* WORKSTATION_TRUST_ACCOUNT | DONT_EXPIRE_PASSWD | TRUSTED_FOR_DELEGATION */
++		uac |= UAC_TRUSTED_FOR_DELEGATION;
++	}
++
++	if (!adcli_enroll_get_dont_expire_password (enroll)) {
++		uac &= ~(UAC_DONT_EXPIRE_PASSWORD);
++	}
++
++	if (asprintf (&uac_str, "%d", uac) < 0) {
++		return_val_if_reached (ADCLI_ERR_UNEXPECTED);
+ 	}
++	vals_userAccountControl[0] = uac_str;
+ 
+ 	ret = calculate_enctypes (enroll, &val);
+ 	if (ret != ADCLI_SUCCESS) {
++		free (uac_str);
+ 		return ret;
+ 	}
+ 	vals_supportedEncryptionTypes[0] = val;
+@@ -868,6 +882,7 @@ create_computer_account (adcli_enroll *enroll,
+ 	mods[m] = NULL;
+ 
+ 	ret = ldap_add_ext_s (ldap, enroll->computer_dn, mods, NULL, NULL);
++	free (uac_str);
+ 	free (val);
+ 
+ 	/*
+@@ -1566,10 +1581,20 @@ static char *get_user_account_control (adcli_enroll *enroll)
+ 		uac = UAC_WORKSTATION_TRUST_ACCOUNT | UAC_DONT_EXPIRE_PASSWORD;
+ 	}
+ 
+-	if (adcli_enroll_get_trusted_for_delegation (enroll)) {
+-		uac |= UAC_TRUSTED_FOR_DELEGATION;
+-	} else {
+-		uac &= ~(UAC_TRUSTED_FOR_DELEGATION);
++	if (enroll->trusted_for_delegation_explicit) {
++		if (adcli_enroll_get_trusted_for_delegation (enroll)) {
++			uac |= UAC_TRUSTED_FOR_DELEGATION;
++		} else {
++			uac &= ~(UAC_TRUSTED_FOR_DELEGATION);
++		}
++	}
++
++	if (enroll->dont_expire_password_explicit) {
++		if (adcli_enroll_get_dont_expire_password (enroll)) {
++			uac |= UAC_DONT_EXPIRE_PASSWORD;
++		} else {
++			uac &= ~(UAC_DONT_EXPIRE_PASSWORD);
++		}
+ 	}
+ 
+ 	if (asprintf (&uac_str, "%d", uac) < 0) {
+@@ -1613,7 +1640,8 @@ update_computer_account (adcli_enroll *enroll)
+ 	}
+ 	free (value);
+ 
+-	if (res == ADCLI_SUCCESS && enroll->trusted_for_delegation_explicit) {
++	if (res == ADCLI_SUCCESS && (enroll->trusted_for_delegation_explicit ||
++	                             enroll->dont_expire_password_explicit)) {
+ 		char *vals_userAccountControl[] = { NULL , NULL };
+ 		LDAPMod userAccountControl = { LDAP_MOD_REPLACE, "userAccountControl", { vals_userAccountControl, } };
+ 		LDAPMod *mods[] = { &userAccountControl, NULL };
+@@ -3194,6 +3222,24 @@ adcli_enroll_set_trusted_for_delegation (adcli_enroll *enroll,
+ 	enroll->trusted_for_delegation_explicit = 1;
+ }
+ 
++bool
++adcli_enroll_get_dont_expire_password (adcli_enroll *enroll)
++{
++	return_val_if_fail (enroll != NULL, false);
++
++	return enroll->dont_expire_password;
++}
++
++void
++adcli_enroll_set_dont_expire_password (adcli_enroll *enroll,
++                                       bool value)
++{
++	return_if_fail (enroll != NULL);
++
++	enroll->dont_expire_password = value;
++	enroll->dont_expire_password_explicit = 1;
++}
++
+ void
+ adcli_enroll_set_description (adcli_enroll *enroll, const char *value)
+ {
+diff --git a/library/adenroll.h b/library/adenroll.h
+index 11a30c8..5190eb6 100644
+--- a/library/adenroll.h
++++ b/library/adenroll.h
+@@ -126,6 +126,10 @@ bool               adcli_enroll_get_trusted_for_delegation (adcli_enroll *enroll
+ void               adcli_enroll_set_trusted_for_delegation (adcli_enroll *enroll,
+                                                             bool value);
+ 
++bool               adcli_enroll_get_dont_expire_password (adcli_enroll *enroll);
++void               adcli_enroll_set_dont_expire_password (adcli_enroll *enroll,
++                                                          bool value);
++
+ const char *       adcli_enroll_get_desciption          (adcli_enroll *enroll);
+ void               adcli_enroll_set_description         (adcli_enroll *enroll,
+                                                          const char *value);
+diff --git a/tools/computer.c b/tools/computer.c
+index 98a0472..954066a 100644
+--- a/tools/computer.c
++++ b/tools/computer.c
+@@ -110,6 +110,7 @@ typedef enum {
+ 	opt_add_samba_data,
+ 	opt_samba_data_tool,
+ 	opt_trusted_for_delegation,
++	opt_dont_expire_password,
+ 	opt_add_service_principal,
+ 	opt_remove_service_principal,
+ 	opt_description,
+@@ -143,6 +144,8 @@ static adcli_tool_desc common_usages[] = {
+ 	{ opt_computer_password_lifetime, "lifetime of the host accounts password in days", },
+ 	{ opt_trusted_for_delegation, "set/unset the TRUSTED_FOR_DELEGATION flag\n"
+ 	                              "in the userAccountControl attribute", },
++	{ opt_dont_expire_password, "set/unset the DONT_EXPIRE_PASSWORD flag\n"
++	                            "in the userAccountControl attribute", },
+ 	{ opt_add_service_principal, "add the given service principal to the account\n" },
+ 	{ opt_remove_service_principal, "remove the given service principal from the account\n" },
+ 	{ opt_description, "add a description to the account\n" },
+@@ -304,6 +307,13 @@ parse_option (Option opt,
+ 			adcli_enroll_set_trusted_for_delegation (enroll, false);
+ 		}
+ 		return ADCLI_SUCCESS;
++	case opt_dont_expire_password:
++		if (strcasecmp (optarg, "true") == 0 || strcasecmp (optarg, "yes") == 0) {
++			adcli_enroll_set_dont_expire_password (enroll, true);
++		} else {
++			adcli_enroll_set_dont_expire_password (enroll, false);
++		}
++		return ADCLI_SUCCESS;
+ 	case opt_add_service_principal:
+ 		adcli_enroll_add_service_principal_to_add (enroll, optarg);
+ 		return ADCLI_SUCCESS;
+@@ -383,6 +393,7 @@ adcli_tool_computer_join (adcli_conn *conn,
+ 		{ "description", optional_argument, NULL, opt_description },
+ 		{ "user-principal", optional_argument, NULL, opt_user_principal },
+ 		{ "trusted-for-delegation", required_argument, NULL, opt_trusted_for_delegation },
++		{ "dont-expire-password", required_argument, NULL, opt_dont_expire_password },
+ 		{ "add-service-principal", required_argument, NULL, opt_add_service_principal },
+ 		{ "show-details", no_argument, NULL, opt_show_details },
+ 		{ "show-password", no_argument, NULL, opt_show_password },
+@@ -504,6 +515,7 @@ adcli_tool_computer_update (adcli_conn *conn,
+ 		{ "user-principal", optional_argument, NULL, opt_user_principal },
+ 		{ "computer-password-lifetime", optional_argument, NULL, opt_computer_password_lifetime },
+ 		{ "trusted-for-delegation", required_argument, NULL, opt_trusted_for_delegation },
++		{ "dont-expire-password", required_argument, NULL, opt_dont_expire_password },
+ 		{ "add-service-principal", required_argument, NULL, opt_add_service_principal },
+ 		{ "remove-service-principal", required_argument, NULL, opt_remove_service_principal },
+ 		{ "show-details", no_argument, NULL, opt_show_details },
+-- 
+2.31.1
+
diff --git a/SOURCES/0002-adcli_entry-add-entry_attrs-with-userAccountControl-.patch b/SOURCES/0002-adcli_entry-add-entry_attrs-with-userAccountControl-.patch
new file mode 100644
index 0000000000000000000000000000000000000000..58d91ec698c961affbe6b1d136882c4180b9f143
--- /dev/null
+++ b/SOURCES/0002-adcli_entry-add-entry_attrs-with-userAccountControl-.patch
@@ -0,0 +1,60 @@
+From 7148ab196d0a96ede9b5ef463b0481d0fe372b21 Mon Sep 17 00:00:00 2001
+From: Sumit Bose <sbose@redhat.com>
+Date: Fri, 11 Jun 2021 12:46:03 +0200
+Subject: [PATCH 2/3] adcli_entry: add entry_attrs with userAccountControl
+ attribute
+
+---
+ library/adentry.c | 8 ++++++--
+ 1 file changed, 6 insertions(+), 2 deletions(-)
+
+diff --git a/library/adentry.c b/library/adentry.c
+index 1cc0518..13dcaf8 100644
+--- a/library/adentry.c
++++ b/library/adentry.c
+@@ -42,6 +42,7 @@ struct _adcli_entry {
+ 	char *entry_dn;
+ 	char *domain_ou;
+ 	char *entry_container;
++	LDAPMessage *entry_attrs;
+ };
+ 
+ static adcli_entry *
+@@ -63,6 +64,7 @@ entry_new (adcli_conn *conn,
+ 
+ 	entry->builder = builder;
+ 	entry->object_class = object_class;
++	entry->entry_attrs = NULL;
+ 	return entry;
+ }
+ 
+@@ -82,6 +84,7 @@ entry_free (adcli_entry *entry)
+ 	free (entry->entry_container);
+ 	free (entry->entry_dn);
+ 	free (entry->domain_ou);
++	ldap_msgfree (entry->entry_attrs);
+ 	adcli_conn_unref (entry->conn);
+ 	free (entry);
+ }
+@@ -102,7 +105,7 @@ static adcli_result
+ update_entry_from_domain (adcli_entry *entry,
+                           LDAP *ldap)
+ {
+-	const char *attrs[] = { "1.1", NULL };
++	const char *attrs[] = { "userAccountControl", NULL };
+ 	LDAPMessage *results;
+ 	LDAPMessage *first;
+ 	const char *base;
+@@ -139,7 +142,8 @@ update_entry_from_domain (adcli_entry *entry,
+ 		return_unexpected_if_fail (entry->entry_dn != NULL);
+ 	}
+ 
+-	ldap_msgfree (results);
++	ldap_msgfree (entry->entry_attrs);
++	entry->entry_attrs = results;
+ 	return ADCLI_SUCCESS;
+ }
+ 
+-- 
+2.31.1
+
diff --git a/SOURCES/0002-computer-add-create-msa-sub-command.patch b/SOURCES/0002-computer-add-create-msa-sub-command.patch
new file mode 100644
index 0000000000000000000000000000000000000000..7aaae8a65cd2b0f235fb0567eb72813d14c47702
--- /dev/null
+++ b/SOURCES/0002-computer-add-create-msa-sub-command.patch
@@ -0,0 +1,646 @@
+From 41379f7ad6a9442dd55cc43d832427911e86db31 Mon Sep 17 00:00:00 2001
+From: Sumit Bose <sbose@redhat.com>
+Date: Fri, 23 Oct 2020 16:53:43 +0200
+Subject: [PATCH 2/7] computer: add create-msa sub-command
+
+Add new sub-command to create a managed service account in AD. This can
+be used if LDAP access to AD is needed but the host is already joined to
+a different domain.
+
+Resolves: https://bugzilla.redhat.com/show_bug.cgi?id=1854112
+---
+ doc/adcli.xml      | 140 ++++++++++++++++++++++++++++++++++++++
+ library/adenroll.c | 164 ++++++++++++++++++++++++++++++++++++++-------
+ tools/computer.c   | 125 ++++++++++++++++++++++++++++++++++
+ tools/tools.c      |   1 +
+ tools/tools.h      |   4 ++
+ 5 files changed, 409 insertions(+), 25 deletions(-)
+
+diff --git a/doc/adcli.xml b/doc/adcli.xml
+index cc44fd8..14921f9 100644
+--- a/doc/adcli.xml
++++ b/doc/adcli.xml
+@@ -98,6 +98,10 @@
+ 		<arg choice="opt">--domain=domain.example.com</arg>
+ 		<arg choice="plain">computer</arg>
+ 	</cmdsynopsis>
++	<cmdsynopsis>
++		<command>adcli create-msa</command>
++		<arg choice="opt">--domain=domain.example.com</arg>
++	</cmdsynopsis>
+ </refsynopsisdiv>
+ 
+ <refsect1 id='general_overview'>
+@@ -885,6 +889,142 @@ Password for Administrator:
+ 
+ </refsect1>
+ 
++<refsect1 id='managed_service_account'>
++	<title>Create a managed service account</title>
++
++	<para><command>adcli create-msa</command> creates a managed service
++	account (MSA) in the given Active Directory domain. This is useful if a
++	computer should not fully join the Active Directory domain but LDAP
++	access is needed. A typical use case is that the computer is already
++	joined an Active Directory domain and needs access to another Active
++	Directory domain in the same or a trusted forest where the host
++	credentials from the joined Active Directory domain are
++	not valid, e.g. there is only a one-way trust.</para>
++
++<programlisting>
++$ adcli create-msa --domain=domain.example.com
++Password for Administrator:
++</programlisting>
++
++	<para>The managed service account, as maintained by adcli, cannot have
++	additional service principals names (SPNs) associated with it. An SPN
++	is defined within the context of a Kerberos service which is tied to a
++	machine account in Active Directory. Since a machine can be joined to a
++	single Active Directory domain, managed service account in a different
++	Active Directory domain will not have the SPNs that otherwise are part
++	of another Active Directory domain's machine.</para>
++
++	<para>Since it is expected that a client will most probably join to the
++	Active Directory domain matching its DNS domain the managed service
++	account will be needed for a different Active directory domain and as a
++	result the Active Directory domain name is a mandatory option. If
++	called with no other options <command>adcli create-msa</command>
++	will use the short hostname with an additional random suffix as
++	computer name to avoid name collisions.</para>
++
++	<para>LDAP attribute sAMAccountName has a limit of 20 characters.
++	However, machine account's NetBIOS name must be at most 16 characters
++	long, including a trailing '$' sign. Since it is not expected that the
++	managed service accounts created by adcli will be used on the NetBIOS
++	level the remaining 4 characters can be used to add uniqueness. Managed
++	service account names will have a suffix of 3 random characters from
++	number and upper- and lowercase ASCII ranges appended to the chosen
++	short host name, using '!' as a separator. For a host with the
++	shortname 'myhost', a managed service account will have a common name
++	(CN attribute) 'myhost!A2c' and a NetBIOS name
++	(sAMAccountName attribute) will be 'myhost!A2c$'. A corresponding
++	Kerberos principal in the Active Directory domain where the managed
++	service account was created would be
++	'myhost!A2c$@DOMAIN.EXAMPLE.COM'.</para>
++
++	<para>A keytab for the managed service account is stored into a file
++	specified with -K option. If it is not specified, the file is named
++	after the default keytab file, with lowercase Active Directory domain
++	of the managed service account as a suffix. On most systems it would be
++	<filename>/etc/krb5.keytab</filename> with a suffix of
++	'domain.example.com', e.g.
++	<filename>/etc/krb5.keytad.domain.example.com</filename>.</para>
++
++	<para><command>adcli create-msa</command> can be called multiple
++	times to reset the password of the managed service account. To identify
++	the right account with the random component in the name the
++	corresponding principal is read from the keytab. If the keytab got
++	deleted <command>adcli</command> will try to identify an existing
++	managed service account with the help of the fully-qualified name, if
++	this fails a new managed service account will be created.</para>
++
++	<para>The managed service account password can be updated with
++<programlisting>
++$ adcli update --domain=domain.example.com --host-keytab=/etc/krb5.keytad.domain.example.com
++</programlisting>
++	and the managed service account can be deleted with
++<programlisting>
++$ adcli delete-computer --domain=domain.example.com 'myhost!A2c'
++</programlisting>
++	</para>
++
++	<para>In addition to the global options, you can specify the following
++	options to control how this operation is done.</para>
++
++	<variablelist>
++		<varlistentry>
++			<term><option>-N, --computer-name=<parameter>computer</parameter></option></term>
++			<listitem><para>The short non-dotted name of the managed
++			service account that will be created in the Active
++			Directory domain. The long option name
++			<option>--computer-name</option> is
++			kept to underline the similarity with the same option
++			of the other sub-commands. If not specified,
++			then the first portion of the <option>--host-fqdn</option>
++			or its default is used with a random suffix.</para></listitem>
++		</varlistentry>
++		<varlistentry>
++			<term><option>-O, --domain-ou=<parameter>OU=xxx</parameter></option></term>
++			<listitem><para>The full distinguished name of the OU in
++			which to create the managed service account. If not
++			specified, then the managed service account will be
++			created in a default location.</para></listitem>
++		</varlistentry>
++		<varlistentry>
++			<term><option>-H, --host-fqdn=<parameter>host</parameter></option></term>
++			<listitem><para>Override the local machine's fully
++			qualified DNS domain name. If not specified, the local
++			machine's hostname will be retrieved via
++			<function>gethostname()</function>.
++			If <function>gethostname()</function> only returns a short name
++			<function>getaddrinfo()</function> with the AI_CANONNAME hint
++			is called to expand the name to a fully qualified DNS
++			domain name.</para></listitem>
++		</varlistentry>
++		<varlistentry>
++			<term><option>-K, --host-keytab=<parameter>/path/to/keytab</parameter></option></term>
++			<listitem><para>Specify the path to the host keytab where
++			credentials of the managed service account will be
++			written after a successful creation. If not specified,
++			the default location will be used, usually
++			<filename>/etc/krb5.keytab</filename> with
++			the lower-cased Active Directory domain name added as a
++			suffix e.g.
++			<filename>/etc/krb5.keytab.domain.example.com</filename>.
++			</para></listitem>
++		</varlistentry>
++		<varlistentry>
++			<term><option>--show-details</option></term>
++			<listitem><para>After a successful creation print out
++			information about the created object. This is output in
++			a format that should be both human and machine
++			readable.</para></listitem>
++		</varlistentry>
++		<varlistentry>
++			<term><option>--show-password</option></term>
++			<listitem><para>After a successful creation print out
++			the managed service account password. This is output in
++			a format that should be both human and machine
++			readable.</para></listitem>
++		</varlistentry>
++	</variablelist>
++</refsect1>
++
+ <refsect1 id='delegation'>
+ 	<title>Delegated Permissions</title>
+ 	<para>It is common practice in AD to not use an account from the Domain
+diff --git a/library/adenroll.c b/library/adenroll.c
+index 5ae1f7b..dbfda36 100644
+--- a/library/adenroll.c
++++ b/library/adenroll.c
+@@ -155,6 +155,20 @@ struct _adcli_enroll {
+ 	char *description;
+ };
+ 
++static void
++check_if_service (adcli_enroll *enroll,
++                  LDAP *ldap,
++                  LDAPMessage *results)
++{
++	char **objectclasses = NULL;
++
++	objectclasses = _adcli_ldap_parse_values (ldap, results, "objectClass");
++	enroll->is_service = _adcli_strv_has_ex (objectclasses,
++	                                         "msDS-ManagedServiceAccount",
++	                                         strcasecmp) == 1 ? true : false;
++	_adcli_strv_free (objectclasses);
++}
++
+ static adcli_result
+ ensure_host_fqdn (adcli_result res,
+                   adcli_enroll *enroll)
+@@ -471,13 +485,15 @@ ensure_keytab_principals (adcli_result res,
+ {
+ 	krb5_context k5;
+ 	krb5_error_code code;
+-	int count;
++	int count = 0;
+ 	int at, i;
+ 
+ 	/* Prepare the principals we're going to add to the keytab */
+ 
+-	return_unexpected_if_fail (enroll->service_principals);
+-	count = _adcli_strv_len (enroll->service_principals);
++	if (!enroll->is_service) {
++		return_unexpected_if_fail (enroll->service_principals);
++		count = _adcli_strv_len (enroll->service_principals);
++	}
+ 
+ 	k5 = adcli_conn_get_krb5_context (enroll->conn);
+ 	return_unexpected_if_fail (k5 != NULL);
+@@ -556,8 +572,12 @@ static adcli_result
+ lookup_computer_container (adcli_enroll *enroll,
+                            LDAP *ldap)
+ {
+-	char *attrs[] = { "wellKnownObjects", NULL };
+-	char *prefix = "B:32:AA312825768811D1ADED00C04FD8D5CD:";
++	char *attrs[] = { enroll->is_service ? "otherWellKnownObjects"
++	                                     : "wellKnownObjects", NULL };
++	const char *prefix = enroll->is_service ? "B:32:1EB93889E40C45DF9F0C64D23BBB6237:"
++	                                        : "B:32:AA312825768811D1ADED00C04FD8D5CD:";
++	const char *filter = enroll->is_service ? "(&(objectClass=container)(cn=Managed Service Accounts))"
++	                                        : "(&(objectClass=container)(cn=Computers))";
+ 	int prefix_len;
+ 	LDAPMessage *results;
+ 	const char *base;
+@@ -586,7 +606,7 @@ lookup_computer_container (adcli_enroll *enroll,
+ 		                                   "Couldn't lookup computer container: %s", base);
+ 	}
+ 
+-	values = _adcli_ldap_parse_values (ldap, results, "wellKnownObjects");
++	values = _adcli_ldap_parse_values (ldap, results, attrs[0]);
+ 	ldap_msgfree (results);
+ 
+ 	prefix_len = strlen (prefix);
+@@ -604,8 +624,7 @@ lookup_computer_container (adcli_enroll *enroll,
+ 
+ 	/* Try harder */
+ 	if (!enroll->computer_container) {
+-		ret = ldap_search_ext_s (ldap, base, LDAP_SCOPE_BASE,
+-		                         "(&(objectClass=container)(cn=Computers))",
++		ret = ldap_search_ext_s (ldap, base, LDAP_SCOPE_BASE, filter,
+ 		                         attrs, 0, NULL, NULL, NULL, -1, &results);
+ 		if (ret == LDAP_SUCCESS) {
+ 			enroll->computer_container = _adcli_ldap_parse_dn (ldap, results);
+@@ -747,7 +766,7 @@ static adcli_result
+ create_computer_account (adcli_enroll *enroll,
+                          LDAP *ldap)
+ {
+-	char *vals_objectClass[] = { "computer", NULL };
++	char *vals_objectClass[] = { enroll->is_service ? "msDS-ManagedServiceAccount" : "computer", NULL };
+ 	LDAPMod objectClass = { LDAP_MOD_ADD, "objectClass", { vals_objectClass, } };
+ 	char *vals_sAMAccountName[] = { enroll->computer_sam, NULL };
+ 	LDAPMod sAMAccountName = { LDAP_MOD_ADD, "sAMAccountName", { vals_sAMAccountName, } };
+@@ -806,7 +825,7 @@ create_computer_account (adcli_enroll *enroll,
+ 	m = 0;
+ 	for (c = 0; c < mods_count - 1; c++) {
+ 		/* Skip empty LDAP sttributes */
+-		if (all_mods[c]->mod_vals.modv_strvals[0] != NULL) {
++		if (all_mods[c]->mod_vals.modv_strvals != NULL && all_mods[c]->mod_vals.modv_strvals[0] != NULL) {
+ 			mods[m++] = all_mods[c];
+ 		}
+ 	}
+@@ -936,7 +955,7 @@ locate_computer_account (adcli_enroll *enroll,
+                          LDAPMessage **rresults,
+                          LDAPMessage **rentry)
+ {
+-	char *attrs[] = { "1.1", NULL };
++	char *attrs[] = { "objectClass", NULL };
+ 	LDAPMessage *results = NULL;
+ 	LDAPMessage *entry = NULL;
+ 	const char *base;
+@@ -948,7 +967,9 @@ locate_computer_account (adcli_enroll *enroll,
+ 	/* If we don't yet know our computer dn, then try and find it */
+ 	value = _adcli_ldap_escape_filter (enroll->computer_sam);
+ 	return_unexpected_if_fail (value != NULL);
+-	if (asprintf (&filter, "(&(objectClass=computer)(sAMAccountName=%s))", value) < 0)
++	if (asprintf (&filter, "(&(objectClass=%s)(sAMAccountName=%s))",
++	              enroll->is_service ? "msDS-ManagedServiceAccount" : "computer",
++	              value) < 0)
+ 		return_unexpected_if_reached ();
+ 	free (value);
+ 
+@@ -962,8 +983,11 @@ locate_computer_account (adcli_enroll *enroll,
+ 	if (ret == LDAP_SUCCESS) {
+ 		entry = ldap_first_entry (ldap, results);
+ 
+-		/* If we found a computer account, make note of dn */
++		/* If we found a computer/service account, make note of dn */
+ 		if (entry) {
++			if (!enroll->is_service_explicit) {
++				check_if_service ( enroll, ldap, results);
++			}
+ 			dn = ldap_get_dn (ldap, entry);
+ 			free (enroll->computer_dn);
+ 			enroll->computer_dn = strdup (dn);
+@@ -1003,7 +1027,7 @@ load_computer_account (adcli_enroll *enroll,
+                        LDAPMessage **rresults,
+                        LDAPMessage **rentry)
+ {
+-	char *attrs[] = { "1.1", NULL };
++	char *attrs[] = { "objectClass", NULL };
+ 	LDAPMessage *results = NULL;
+ 	LDAPMessage *entry = NULL;
+ 	int ret;
+@@ -1081,6 +1105,12 @@ locate_or_create_computer_account (adcli_enroll *enroll,
+ 	if (res == ADCLI_SUCCESS && entry == NULL)
+ 		res = create_computer_account (enroll, ldap);
+ 
++	/* Service account already exists, just continue and update the
++	 * password */
++	if (enroll->is_service && entry != NULL) {
++		res = ADCLI_SUCCESS;
++	}
++
+ 	if (results)
+ 		ldap_msgfree (results);
+ 
+@@ -1413,6 +1443,11 @@ update_computer_account (adcli_enroll *enroll)
+ 	LDAP *ldap;
+ 	char *value = NULL;
+ 
++	/* No updates for service accounts */
++	if (enroll->is_service) {
++		return;
++	}
++
+ 	ldap = adcli_conn_get_ldap_connection (enroll->conn);
+ 	return_if_fail (ldap != NULL);
+ 
+@@ -1501,6 +1536,11 @@ update_service_principals (adcli_enroll *enroll)
+ 	LDAP *ldap;
+ 	int ret;
+ 
++	/* No updates for service accounts */
++	if (enroll->is_service) {
++		return ADCLI_SUCCESS;
++	}
++
+ 	ldap = adcli_conn_get_ldap_connection (enroll->conn);
+ 	return_unexpected_if_fail (ldap != NULL);
+ 
+@@ -1614,6 +1654,8 @@ load_keytab_entry (krb5_context k5,
+ 			enroll->computer_name = name;
+ 			name[len - 1] = '\0';
+ 			_adcli_info ("Found computer name in keytab: %s", name);
++			adcli_conn_set_computer_name (enroll->conn,
++			                              enroll->computer_name);
+ 			name = NULL;
+ 
+ 		} else if (!enroll->host_fqdn && _adcli_str_has_prefix (name, "host/") && strchr (name, '.')) {
+@@ -2002,17 +2044,25 @@ adcli_enroll_prepare (adcli_enroll *enroll,
+ 
+ 	adcli_clear_last_error ();
+ 
+-	/* Basic discovery and figuring out enroll params */
+-	res = ensure_host_fqdn (res, enroll);
+-	res = ensure_computer_name (res, enroll);
+-	res = ensure_computer_sam (res, enroll);
+-	res = ensure_user_principal (res, enroll);
+-	res = ensure_computer_password (res, enroll);
+-	if (!(flags & ADCLI_ENROLL_NO_KEYTAB))
++	if (enroll->is_service) {
++		/* Ensure basic params for service accounts */
++		res = ensure_computer_sam (res, enroll);
++		res = ensure_computer_password (res, enroll);
+ 		res = ensure_host_keytab (res, enroll);
+-	res = ensure_service_names (res, enroll);
+-	res = ensure_service_principals (res, enroll);
+-	res = ensure_keytab_principals (res, enroll);
++		res = ensure_keytab_principals (res, enroll);
++	} else {
++		/* Basic discovery and figuring out enroll params */
++		res = ensure_host_fqdn (res, enroll);
++		res = ensure_computer_name (res, enroll);
++		res = ensure_computer_sam (res, enroll);
++		res = ensure_user_principal (res, enroll);
++		res = ensure_computer_password (res, enroll);
++		if (!(flags & ADCLI_ENROLL_NO_KEYTAB))
++			res = ensure_host_keytab (res, enroll);
++		res = ensure_service_names (res, enroll);
++		res = ensure_service_principals (res, enroll);
++		res = ensure_keytab_principals (res, enroll);
++	}
+ 
+ 	return res;
+ }
+@@ -2157,6 +2207,58 @@ enroll_join_or_update_tasks (adcli_enroll *enroll,
+ 	return update_keytab_for_principals (enroll, flags);
+ }
+ 
++static adcli_result
++adcli_enroll_add_description_for_service_account (adcli_enroll *enroll)
++{
++	const char *fqdn;
++	char *desc;
++
++	fqdn = adcli_conn_get_host_fqdn (enroll->conn);
++	return_unexpected_if_fail (fqdn != NULL);
++	if (asprintf (&desc, "Please do not edit, Service account for %s, "
++	                     "managed by adcli.", fqdn) < 0) {
++		return_unexpected_if_reached ();
++	}
++
++	adcli_enroll_set_description (enroll, desc);
++	free (desc);
++
++	return ADCLI_SUCCESS;
++}
++
++static adcli_result
++adcli_enroll_add_keytab_for_service_account (adcli_enroll *enroll)
++{
++	krb5_context k5;
++	krb5_error_code code;
++	char def_keytab_name[MAX_KEYTAB_NAME_LEN];
++	char *lc_dom_name;
++	int ret;
++
++	if (adcli_enroll_get_keytab_name (enroll) == NULL) {
++		k5 = adcli_conn_get_krb5_context (enroll->conn);
++		return_unexpected_if_fail (k5 != NULL);
++
++		code = krb5_kt_default_name (k5, def_keytab_name,
++		                             sizeof (def_keytab_name));
++		return_unexpected_if_fail (code == 0);
++
++		lc_dom_name = strdup (adcli_conn_get_domain_name (enroll->conn));
++		return_unexpected_if_fail (lc_dom_name != NULL);
++		_adcli_str_down (lc_dom_name);
++
++
++		ret = asprintf (&enroll->keytab_name, "%s.%s", def_keytab_name,
++		                                             lc_dom_name);
++		free (lc_dom_name);
++		return_unexpected_if_fail (ret > 0);
++	}
++
++	_adcli_info ("Using service account keytab: %s", enroll->keytab_name);
++
++	return ADCLI_SUCCESS;
++}
++
+ adcli_result
+ adcli_enroll_join (adcli_enroll *enroll,
+                    adcli_enroll_flags flags)
+@@ -2172,7 +2274,14 @@ adcli_enroll_join (adcli_enroll *enroll,
+ 	if (res != ADCLI_SUCCESS)
+ 		return res;
+ 
+-	res = ensure_default_service_names (enroll);
++	if (enroll->is_service) {
++		res = adcli_enroll_add_description_for_service_account (enroll);
++		if (res == ADCLI_SUCCESS) {
++			res = adcli_enroll_add_keytab_for_service_account (enroll);
++		}
++	} else {
++		res = ensure_default_service_names (enroll);
++	}
+ 	if (res != ADCLI_SUCCESS)
+ 		return res;
+ 
+@@ -2281,6 +2390,11 @@ adcli_enroll_update (adcli_enroll *enroll,
+ 	}
+ 	free (value);
+ 
++	/* We only support password changes for service accounts */
++	if (enroll->is_service && (flags & ADCLI_ENROLL_PASSWORD_VALID)) {
++		return ADCLI_SUCCESS;
++	}
++
+ 	return enroll_join_or_update_tasks (enroll, flags);
+ }
+ 
+diff --git a/tools/computer.c b/tools/computer.c
+index 5a97d8b..63fd374 100644
+--- a/tools/computer.c
++++ b/tools/computer.c
+@@ -1074,3 +1074,128 @@ adcli_tool_computer_show (adcli_conn *conn,
+ 	adcli_enroll_unref (enroll);
+ 	return 0;
+ }
++
++int
++adcli_tool_computer_managed_service_account (adcli_conn *conn,
++                                             int argc,
++                                             char *argv[])
++{
++	adcli_enroll *enroll;
++	adcli_result res;
++	int show_password = 0;
++	int details = 0;
++	int opt;
++
++	struct option options[] = {
++		{ "domain", required_argument, NULL, opt_domain },
++		{ "domain-realm", required_argument, NULL, opt_domain_realm },
++		{ "domain-controller", required_argument, NULL, opt_domain_controller },
++		{ "use-ldaps", no_argument, 0, opt_use_ldaps },
++		{ "login-user", required_argument, NULL, opt_login_user },
++		{ "login-ccache", optional_argument, NULL, opt_login_ccache },
++		{ "host-fqdn", required_argument, 0, opt_host_fqdn },
++		{ "computer-name", required_argument, 0, opt_computer_name },
++		{ "host-keytab", required_argument, 0, opt_host_keytab },
++		{ "no-password", no_argument, 0, opt_no_password },
++		{ "stdin-password", no_argument, 0, opt_stdin_password },
++		{ "prompt-password", no_argument, 0, opt_prompt_password },
++		{ "domain-ou", required_argument, NULL, opt_domain_ou },
++		{ "show-details", no_argument, NULL, opt_show_details },
++		{ "show-password", no_argument, NULL, opt_show_password },
++		{ "verbose", no_argument, NULL, opt_verbose },
++		{ "help", no_argument, NULL, 'h' },
++		{ 0 },
++	};
++
++	static adcli_tool_desc usages[] = {
++		{ 0, "usage: adcli create-msa --domain=xxxx" },
++		{ 0 },
++	};
++
++	enroll = adcli_enroll_new (conn);
++	if (enroll == NULL) {
++		warnx ("unexpected memory problems");
++		return -1;
++	}
++
++	while ((opt = adcli_tool_getopt (argc, argv, options)) != -1) {
++		switch (opt) {
++		case opt_one_time_password:
++			adcli_conn_set_allowed_login_types (conn, ADCLI_LOGIN_COMPUTER_ACCOUNT);
++			adcli_conn_set_computer_password (conn, optarg);
++			break;
++		case opt_show_details:
++			details = 1;
++			break;
++		case opt_show_password:
++			show_password = 1;
++			break;
++		case 'h':
++		case '?':
++		case ':':
++			adcli_tool_usage (options, usages);
++			adcli_tool_usage (options, common_usages);
++			adcli_enroll_unref (enroll);
++			return opt == 'h' ? 0 : 2;
++		default:
++			res = parse_option ((Option)opt, optarg, conn, enroll);
++			if (res != ADCLI_SUCCESS) {
++				adcli_enroll_unref (enroll);
++				return res;
++			}
++			break;
++		}
++	}
++
++	argc -= optind;
++	argv += optind;
++
++	if (argc == 1)
++		adcli_conn_set_domain_name (conn, argv[0]);
++	else if (argc > 1) {
++		warnx ("extra arguments specified");
++		adcli_enroll_unref (enroll);
++		return 2;
++	}
++
++	if (adcli_conn_get_domain_name (conn) == NULL) {
++		warnx ("domain name is required");
++		adcli_enroll_unref (enroll);
++		return 2;
++	}
++
++	adcli_enroll_set_is_service (enroll, true);
++	adcli_conn_set_allowed_login_types (conn, ADCLI_LOGIN_USER_ACCOUNT);
++
++	res = adcli_enroll_load (enroll);
++	if (res != ADCLI_SUCCESS) {
++		/* ignored */
++	}
++
++	res = adcli_conn_connect (conn);
++	if (res != ADCLI_SUCCESS) {
++		warnx ("couldn't connect to %s domain: %s",
++		       adcli_conn_get_domain_name (conn),
++		       adcli_get_last_error ());
++		adcli_enroll_unref (enroll);
++		return -res;
++	}
++
++	res = adcli_enroll_join (enroll, 0);
++	if (res != ADCLI_SUCCESS) {
++		warnx ("Adding service account for %s failed: %s",
++		       adcli_conn_get_domain_name (conn),
++		       adcli_get_last_error ());
++		adcli_enroll_unref (enroll);
++		return -res;
++	}
++
++	if (details)
++		dump_details (conn, enroll, show_password);
++	else if (show_password)
++		dump_password (conn, enroll);
++
++	adcli_enroll_unref (enroll);
++
++	return 0;
++}
+diff --git a/tools/tools.c b/tools/tools.c
+index 1b6d879..d0dcf98 100644
+--- a/tools/tools.c
++++ b/tools/tools.c
+@@ -60,6 +60,7 @@ struct {
+ 	{ "reset-computer", adcli_tool_computer_reset, "Reset a computer account", },
+ 	{ "delete-computer", adcli_tool_computer_delete, "Delete a computer account", },
+ 	{ "show-computer", adcli_tool_computer_show, "Show computer account attributes stored in AD", },
++	{ "create-msa", adcli_tool_computer_managed_service_account, "Create a managed service account in the given AD domain", },
+ 	{ "create-user", adcli_tool_user_create, "Create a user account", },
+ 	{ "delete-user", adcli_tool_user_delete, "Delete a user account", },
+ 	{ "create-group", adcli_tool_group_create, "Create a group", },
+diff --git a/tools/tools.h b/tools/tools.h
+index 3702875..82d5e4e 100644
+--- a/tools/tools.h
++++ b/tools/tools.h
+@@ -82,6 +82,10 @@ int       adcli_tool_computer_show     (adcli_conn *conn,
+                                         int argc,
+                                         char *argv[]);
+ 
++int       adcli_tool_computer_managed_service_account (adcli_conn *conn,
++                                                       int argc,
++                                                       char *argv[]);
++
+ int       adcli_tool_user_create       (adcli_conn *conn,
+                                         int argc,
+                                         char *argv[]);
+-- 
+2.28.0
+
diff --git a/SOURCES/0003-enroll-use-computer-or-service-in-debug-messages.patch b/SOURCES/0003-enroll-use-computer-or-service-in-debug-messages.patch
new file mode 100644
index 0000000000000000000000000000000000000000..4e5aea5ab398898337912c91a9a9032b9b60ddc9
--- /dev/null
+++ b/SOURCES/0003-enroll-use-computer-or-service-in-debug-messages.patch
@@ -0,0 +1,358 @@
+From eea6a8071b5e5df74808903bb15b30acf820ce3f Mon Sep 17 00:00:00 2001
+From: Sumit Bose <sbose@redhat.com>
+Date: Fri, 23 Oct 2020 16:55:11 +0200
+Subject: [PATCH 3/7] enroll: use 'computer' or 'service' in debug messages
+
+Use proper account type in debug messages.
+
+Resolves: https://bugzilla.redhat.com/show_bug.cgi?id=1854112
+---
+ library/adenroll.c | 115 ++++++++++++++++++++++++++++-----------------
+ 1 file changed, 72 insertions(+), 43 deletions(-)
+
+diff --git a/library/adenroll.c b/library/adenroll.c
+index dbfda36..9cdc79b 100644
+--- a/library/adenroll.c
++++ b/library/adenroll.c
+@@ -155,6 +155,12 @@ struct _adcli_enroll {
+ 	char *description;
+ };
+ 
++static const char *
++s_or_c (adcli_enroll *enroll)
++{
++	return enroll->is_service ? "service" : "computer";
++}
++
+ static void
+ check_if_service (adcli_enroll *enroll,
+                   LDAP *ldap,
+@@ -203,13 +209,15 @@ ensure_computer_name (adcli_result res,
+ 		return res;
+ 
+ 	if (enroll->computer_name) {
+-		_adcli_info ("Enrolling computer name: %s",
++		_adcli_info ("Enrolling %s name: %s",
++		             s_or_c (enroll),
+ 		             enroll->computer_name);
+ 		return ADCLI_SUCCESS;
+ 	}
+ 
+ 	if (!enroll->host_fqdn) {
+-		_adcli_err ("No host name from which to determine the computer name");
++		_adcli_err ("No host name from which to determine the %s name",
++		            s_or_c (enroll));
+ 		return ADCLI_ERR_CONFIG;
+ 	}
+ 
+@@ -603,7 +611,8 @@ lookup_computer_container (adcli_enroll *enroll,
+ 
+ 	} else if (ret != LDAP_SUCCESS) {
+ 		return _adcli_ldap_handle_failure (ldap, ADCLI_ERR_DIRECTORY,
+-		                                   "Couldn't lookup computer container: %s", base);
++		                                   "Couldn't lookup %s container: %s",
++		                                   s_or_c (enroll), base);
+ 	}
+ 
+ 	values = _adcli_ldap_parse_values (ldap, results, attrs[0]);
+@@ -614,8 +623,8 @@ lookup_computer_container (adcli_enroll *enroll,
+ 		if (strncmp (values[i], prefix, prefix_len) == 0) {
+ 			enroll->computer_container = strdup (values[i] + prefix_len);
+ 			return_unexpected_if_fail (enroll->computer_container != NULL);
+-			_adcli_info ("Found well known computer container at: %s",
+-			             enroll->computer_container);
++			_adcli_info ("Found well known %s container at: %s",
++			             s_or_c (enroll), enroll->computer_container);
+ 			break;
+ 		}
+ 	}
+@@ -629,8 +638,9 @@ lookup_computer_container (adcli_enroll *enroll,
+ 		if (ret == LDAP_SUCCESS) {
+ 			enroll->computer_container = _adcli_ldap_parse_dn (ldap, results);
+ 			if (enroll->computer_container) {
+-				_adcli_info ("Well known computer container not "
++				_adcli_info ("Well known %s container not "
+ 				             "found, but found suitable one at: %s",
++				             s_or_c (enroll),
+ 				             enroll->computer_container);
+ 			}
+ 		}
+@@ -646,7 +656,8 @@ lookup_computer_container (adcli_enroll *enroll,
+ 	}
+ 
+ 	if (!enroll->computer_container) {
+-		_adcli_err ("Couldn't find location to create computer accounts");
++		_adcli_err ("Couldn't find location to create %s accounts",
++		            s_or_c (enroll));
+ 		return ADCLI_ERR_DIRECTORY;
+ 	}
+ 
+@@ -674,7 +685,8 @@ calculate_computer_account (adcli_enroll *enroll,
+ 	if (asprintf (&enroll->computer_dn, "CN=%s,%s", enroll->computer_name, enroll->computer_container) < 0)
+ 		return_unexpected_if_reached ();
+ 
+-	_adcli_info ("Calculated computer account: %s", enroll->computer_dn);
++	_adcli_info ("Calculated %s account: %s",
++	             s_or_c (enroll), enroll->computer_dn);
+ 	return ADCLI_SUCCESS;
+ }
+ 
+@@ -861,7 +873,8 @@ create_computer_account (adcli_enroll *enroll,
+ 		                                   enroll->computer_dn);
+ 	}
+ 
+-	_adcli_info ("Created computer account: %s", enroll->computer_dn);
++	_adcli_info ("Created %s account: %s", s_or_c (enroll),
++	                                       enroll->computer_dn);
+ 	return ADCLI_SUCCESS;
+ }
+ 
+@@ -908,17 +921,17 @@ validate_computer_account (adcli_enroll *enroll,
+ 	assert (enroll->computer_dn != NULL);
+ 
+ 	if (already_exists && !allow_overwrite) {
+-		_adcli_err ("The computer account %s already exists",
+-		            enroll->computer_name);
++		_adcli_err ("The %s account %s already exists",
++		            s_or_c (enroll), enroll->computer_name);
+ 		return ADCLI_ERR_CONFIG;
+ 	}
+ 
+ 	/* Do we have an explicitly requested ou? */
+ 	if (enroll->domain_ou && enroll->domain_ou_explicit && already_exists) {
+ 		if (!_adcli_ldap_dn_has_ancestor (enroll->computer_dn, enroll->domain_ou)) {
+-			_adcli_err ("The computer account %s already exists, "
++			_adcli_err ("The %s account %s already exists, "
+ 			            "but is not in the desired organizational unit.",
+-			            enroll->computer_name);
++			            s_or_c (enroll), enroll->computer_name);
+ 			return ADCLI_ERR_CONFIG;
+ 		}
+ 	}
+@@ -943,7 +956,8 @@ delete_computer_account (adcli_enroll *enroll,
+ 		                                   "Couldn't delete computer account: %s",
+ 		                                   enroll->computer_dn);
+ 	} else {
+-		_adcli_info ("Deleted computer account at: %s", enroll->computer_dn);
++		_adcli_info ("Deleted %s account at: %s", s_or_c (enroll),
++		                                          enroll->computer_dn);
+ 	}
+ 
+ 	return ADCLI_SUCCESS;
+@@ -992,20 +1006,21 @@ locate_computer_account (adcli_enroll *enroll,
+ 			free (enroll->computer_dn);
+ 			enroll->computer_dn = strdup (dn);
+ 			return_unexpected_if_fail (enroll->computer_dn != NULL);
+-			_adcli_info ("Found computer account for %s at: %s",
+-			             enroll->computer_sam, dn);
++			_adcli_info ("Found %s account for %s at: %s",
++			             s_or_c (enroll), enroll->computer_sam, dn);
+ 			ldap_memfree (dn);
+ 
+ 		} else {
+ 			ldap_msgfree (results);
+ 			results = NULL;
+-			_adcli_info ("Computer account for %s does not exist",
+-			             enroll->computer_sam);
++			_adcli_info ("A %s account for %s does not exist",
++			             s_or_c (enroll), enroll->computer_sam);
+ 		}
+ 
+ 	} else {
+ 		return _adcli_ldap_handle_failure (ldap, ADCLI_ERR_DIRECTORY,
+-		                                   "Couldn't lookup computer account: %s",
++		                                   "Couldn't lookup %s account: %s",
++		                                   s_or_c (enroll),
+ 		                                   enroll->computer_sam);
+ 	}
+ 
+@@ -1039,7 +1054,9 @@ load_computer_account (adcli_enroll *enroll,
+ 	if (ret == LDAP_SUCCESS) {
+ 		entry = ldap_first_entry (ldap, results);
+ 		if (entry) {
+-			_adcli_info ("Found computer account for %s at: %s",
++			check_if_service (enroll, ldap, results);
++			_adcli_info ("Found %s account for %s at: %s",
++			             s_or_c (enroll),
+ 			             enroll->computer_sam, enroll->computer_dn);
+ 		}
+ 
+@@ -1146,7 +1163,8 @@ set_password_with_user_creds (adcli_enroll *enroll)
+ 	                                       &result_code_string, &result_string);
+ 
+ 	if (code != 0) {
+-		_adcli_err ("Couldn't set password for computer account: %s: %s",
++		_adcli_err ("Couldn't set password for %s account: %s: %s",
++		            s_or_c (enroll),
+ 		            enroll->computer_sam, krb5_get_error_message (k5, code));
+ 		/* TODO: Parse out these values */
+ 		res = ADCLI_ERR_DIRECTORY;
+@@ -1160,7 +1178,8 @@ set_password_with_user_creds (adcli_enroll *enroll)
+ 		if (result_string.length)
+ 			message = _adcli_str_dupn (result_string.data, result_string.length);
+ #endif
+-		_adcli_err ("Cannot set computer password: %.*s%s%s",
++		_adcli_err ("Cannot set %s password: %.*s%s%s",
++		            s_or_c (enroll),
+ 		            (int)result_code_string.length, result_code_string.data,
+ 		            message ? ": " : "", message ? message : "");
+ 		res = ADCLI_ERR_CREDENTIALS;
+@@ -1170,7 +1189,7 @@ set_password_with_user_creds (adcli_enroll *enroll)
+ 		free (message);
+ #endif
+ 	} else {
+-		_adcli_info ("Set computer password");
++		_adcli_info ("Set %s password", s_or_c (enroll));
+ 		if (enroll->kvno > 0) {
+ 			enroll->kvno++;
+ 			_adcli_info ("kvno incremented to %d", enroll->kvno);
+@@ -1203,7 +1222,8 @@ set_password_with_computer_creds (adcli_enroll *enroll)
+ 
+ 	code = _adcli_kinit_computer_creds (enroll->conn, "kadmin/changepw", NULL, &creds);
+ 	if (code != 0) {
+-		_adcli_err ("Couldn't get change password ticket for computer account: %s: %s",
++		_adcli_err ("Couldn't get change password ticket for %s account: %s: %s",
++		            s_or_c (enroll),
+ 		            enroll->computer_sam, krb5_get_error_message (k5, code));
+ 		return ADCLI_ERR_DIRECTORY;
+ 	}
+@@ -1214,7 +1234,8 @@ set_password_with_computer_creds (adcli_enroll *enroll)
+ 	krb5_free_cred_contents (k5, &creds);
+ 
+ 	if (code != 0) {
+-		_adcli_err ("Couldn't change password for computer account: %s: %s",
++		_adcli_err ("Couldn't change password for %s account: %s: %s",
++		            s_or_c (enroll),
+ 		            enroll->computer_sam, krb5_get_error_message (k5, code));
+ 		/* TODO: Parse out these values */
+ 		res = ADCLI_ERR_DIRECTORY;
+@@ -1284,7 +1305,8 @@ retrieve_computer_account (adcli_enroll *enroll)
+ 
+ 	if (ret != LDAP_SUCCESS) {
+ 		return _adcli_ldap_handle_failure (ldap, ADCLI_ERR_DIRECTORY,
+-		                                   "Couldn't retrieve computer account info: %s",
++		                                   "Couldn't retrieve %s account info: %s",
++		                                   s_or_c (enroll),
+ 		                                   enroll->computer_dn);
+ 	}
+ 
+@@ -1294,15 +1316,15 @@ retrieve_computer_account (adcli_enroll *enroll)
+ 		if (value != NULL) {
+ 			kvno = strtoul (value, &end, 10);
+ 			if (end == NULL || *end != '\0') {
+-				_adcli_err ("Invalid kvno '%s' for computer account in directory: %s",
+-				            value, enroll->computer_dn);
++				_adcli_err ("Invalid kvno '%s' for %s account in directory: %s",
++				            value, s_or_c (enroll), enroll->computer_dn);
+ 				res = ADCLI_ERR_DIRECTORY;
+ 
+ 			} else {
+ 				enroll->kvno = kvno;
+ 
+-				_adcli_info ("Retrieved kvno '%s' for computer account in directory: %s",
+-				             value, enroll->computer_dn);
++				_adcli_info ("Retrieved kvno '%s' for %s account in directory: %s",
++				             value, s_or_c (enroll), enroll->computer_dn);
+ 			}
+ 
+ 			free (value);
+@@ -1311,8 +1333,8 @@ retrieve_computer_account (adcli_enroll *enroll)
+ 			/* Apparently old AD didn't have this attribute, use zero */
+ 			enroll->kvno = 0;
+ 
+-			_adcli_info ("No kvno found for computer account in directory: %s",
+-			             enroll->computer_dn);
++			_adcli_info ("No kvno found for %s account in directory: %s",
++			             s_or_c (enroll), enroll->computer_dn);
+ 		}
+ 	}
+ 
+@@ -1353,12 +1375,14 @@ update_and_calculate_enctypes (adcli_enroll *enroll)
+ 
+ 	if (ret == LDAP_INSUFFICIENT_ACCESS) {
+ 		return _adcli_ldap_handle_failure (ldap, ADCLI_ERR_CREDENTIALS,
+-		                                   "Insufficient permissions to set encryption types on computer account: %s",
++		                                   "Insufficient permissions to set encryption types on %s account: %s",
++		                                   s_or_c (enroll),
+ 		                                   enroll->computer_dn);
+ 
+ 	} else if (ret != LDAP_SUCCESS) {
+ 		return _adcli_ldap_handle_failure (ldap, ADCLI_ERR_DIRECTORY,
+-		                                   "Couldn't set encryption types on computer account: %s",
++		                                   "Couldn't set encryption types on %s account: %s",
++		                                   s_or_c (enroll),
+ 		                                   enroll->computer_dn);
+ 	}
+ 
+@@ -1381,13 +1405,14 @@ update_computer_attribute (adcli_enroll *enroll,
+ 	string = _adcli_ldap_mods_to_string (mods);
+ 	return_unexpected_if_fail (string != NULL);
+ 
+-	_adcli_info ("Modifying computer account: %s", string);
++	_adcli_info ("Modifying %s account: %s", s_or_c (enroll), string);
+ 
+ 	ret = ldap_modify_ext_s (ldap, enroll->computer_dn, mods, NULL, NULL);
+ 
+ 	if (ret != LDAP_SUCCESS) {
+-		_adcli_warn ("Couldn't set %s on computer account: %s: %s",
+-		             string, enroll->computer_dn, ldap_err2string (ret));
++		_adcli_warn ("Couldn't set %s on %s account: %s: %s",
++		             string, s_or_c (enroll), enroll->computer_dn,
++		             ldap_err2string (ret));
+ 		res = ADCLI_ERR_DIRECTORY;
+ 	}
+ 
+@@ -1411,8 +1436,8 @@ static char *get_user_account_control (adcli_enroll *enroll)
+ 
+ 		attr_val = strtoul (uac_str, &end, 10);
+ 		if (*end != '\0' || attr_val > UINT32_MAX) {
+-			_adcli_warn ("Invalid userAccountControl '%s' for computer account in directory: %s, assuming 0",
+-			            uac_str, enroll->computer_dn);
++			_adcli_warn ("Invalid userAccountControl '%s' for %s account in directory: %s, assuming 0",
++			            uac_str, s_or_c (enroll), enroll->computer_dn);
+ 		} else {
+ 			uac = attr_val;
+ 		}
+@@ -1653,7 +1678,8 @@ load_keytab_entry (krb5_context k5,
+ 		    _adcli_str_has_suffix (name, "$") && !strchr (name, '/')) {
+ 			enroll->computer_name = name;
+ 			name[len - 1] = '\0';
+-			_adcli_info ("Found computer name in keytab: %s", name);
++			_adcli_info ("Found %s name in keytab: %s",
++			             s_or_c (enroll), name);
+ 			adcli_conn_set_computer_name (enroll->conn,
+ 			                              enroll->computer_name);
+ 			name = NULL;
+@@ -2348,7 +2374,8 @@ adcli_enroll_read_computer_account (adcli_enroll *enroll,
+ 		if (res != ADCLI_SUCCESS)
+ 			return res;
+ 		if (!enroll->computer_dn) {
+-			_adcli_err ("No computer account for %s exists", enroll->computer_sam);
++			_adcli_err ("No %s account for %s exists",
++			            s_or_c (enroll), enroll->computer_sam);
+ 			return ADCLI_ERR_CONFIG;
+ 		}
+ 	}
+@@ -2460,7 +2487,8 @@ adcli_enroll_delete (adcli_enroll *enroll,
+ 		if (res != ADCLI_SUCCESS)
+ 			return res;
+ 		if (!enroll->computer_dn) {
+-			_adcli_err ("No computer account for %s exists",
++			_adcli_err ("No %s account for %s exists",
++			            s_or_c (enroll),
+ 			            enroll->computer_sam);
+ 			return ADCLI_ERR_CONFIG;
+ 		}
+@@ -2503,7 +2531,8 @@ adcli_enroll_password (adcli_enroll *enroll,
+ 		if (res != ADCLI_SUCCESS)
+ 			return res;
+ 		if (!enroll->computer_dn) {
+-			_adcli_err ("No computer account for %s exists",
++			_adcli_err ("No %s account for %s exists",
++			            s_or_c (enroll),
+ 			            enroll->computer_sam);
+ 			return ADCLI_ERR_CONFIG;
+ 		}
+-- 
+2.28.0
+
diff --git a/SOURCES/0003-entry-add-passwd-user-sub-command.patch b/SOURCES/0003-entry-add-passwd-user-sub-command.patch
new file mode 100644
index 0000000000000000000000000000000000000000..413d7ab677d243524fa169b5cc2533536bb4eef2
--- /dev/null
+++ b/SOURCES/0003-entry-add-passwd-user-sub-command.patch
@@ -0,0 +1,366 @@
+From 6a673b236dfdfdf9c73cc3d2ccf3949eb1a5ddd0 Mon Sep 17 00:00:00 2001
+From: Sumit Bose <sbose@redhat.com>
+Date: Fri, 11 Jun 2021 12:47:37 +0200
+Subject: [PATCH 3/3] entry: add passwd-user sub-command
+
+The new command allows to set or reset a user password with the help of
+an account privileged to set the password.
+
+Resolves: https://bugzilla.redhat.com/show_bug.cgi?id=1952828
+---
+ doc/adcli.xml     |  20 +++++++
+ library/adentry.c | 138 ++++++++++++++++++++++++++++++++++++++++++++++
+ library/adentry.h |   3 +
+ tools/entry.c     |  99 +++++++++++++++++++++++++++++++++
+ tools/tools.c     |   1 +
+ tools/tools.h     |   4 ++
+ 6 files changed, 265 insertions(+)
+
+diff --git a/doc/adcli.xml b/doc/adcli.xml
+index 1ed5d3f..6c36297 100644
+--- a/doc/adcli.xml
++++ b/doc/adcli.xml
+@@ -56,6 +56,11 @@
+ 		<arg choice="opt">--domain=domain.example.com</arg>
+ 		<arg choice="plain">user</arg>
+ 	</cmdsynopsis>
++	<cmdsynopsis>
++		<command>adcli passwd-user</command>
++		<arg choice="opt">--domain=domain.example.com</arg>
++		<arg choice="plain">user</arg>
++	</cmdsynopsis>
+ 	<cmdsynopsis>
+ 		<command>adcli create-group</command>
+ 		<arg choice="opt">--domain=domain.example.com</arg>
+@@ -696,6 +701,21 @@ $ adcli delete-user Fry --domain=domain.example.com
+ 
+ </refsect1>
+ 
++<refsect1 id='passwd_user'>
++	<title>(Re)setting the password of a User with an Administrative Account</title>
++
++	<para><command>adcli passwd-user</command> sets or resets the password
++	of user account. The administrative account used for this operation
++	must have privileges to set a password.</para>
++
++<programlisting>
++$ adcli passwd-user Fry --domain=domain.example.com
++</programlisting>
++
++	<para>The various global options can be used.</para>
++
++</refsect1>
++
+ 
+ <refsect1 id='create_group'>
+ 	<title>Creating a Group</title>
+diff --git a/library/adentry.c b/library/adentry.c
+index 13dcaf8..0d9b9af 100644
+--- a/library/adentry.c
++++ b/library/adentry.c
+@@ -409,6 +409,144 @@ adcli_entry_delete (adcli_entry *entry)
+ 	return ADCLI_SUCCESS;
+ }
+ 
++static adcli_result
++adcli_entry_ensure_enabled (adcli_entry *entry)
++{
++	adcli_result res;
++	LDAP *ldap;
++	adcli_attrs *attrs;
++	uint32_t uac = 0;
++	char *uac_str;
++	unsigned long attr_val;
++	char *end;
++
++	return_unexpected_if_fail (entry->entry_attrs != NULL);
++
++	ldap = adcli_conn_get_ldap_connection (entry->conn);
++	return_unexpected_if_fail (ldap != NULL);
++
++	uac_str = _adcli_ldap_parse_value (ldap, entry->entry_attrs,
++	                                   "userAccountControl");
++	if (uac_str != NULL) {
++		attr_val = strtoul (uac_str, &end, 10);
++		if (*end != '\0' || attr_val > UINT32_MAX) {
++			_adcli_warn ("Invalid userAccountControl '%s' for %s account in directory: %s, assuming 0",
++			            uac_str, entry->object_class, entry->entry_dn);
++		} else {
++			uac = attr_val;
++		}
++		free (uac_str);
++	}
++	if (uac & UAC_ACCOUNTDISABLE) {
++		uac &= ~(UAC_ACCOUNTDISABLE);
++
++		if (asprintf (&uac_str, "%d", uac) < 0) {
++			_adcli_warn ("Cannot enable %s entry %s after password (re)set",
++			             entry->object_class, entry->entry_dn);
++			return ADCLI_ERR_UNEXPECTED;
++		}
++
++		attrs = adcli_attrs_new ();
++		adcli_attrs_replace (attrs, "userAccountControl", uac_str,
++		                     NULL);
++		res = adcli_entry_modify (entry, attrs);
++		if (res == ADCLI_SUCCESS) {
++			_adcli_info ("Enabled %s entry %s after password (re)set",
++			             entry->object_class, entry->entry_dn);
++		} else {
++			_adcli_warn ("Failed to enable %s entry %s after password (re)set",
++			             entry->object_class, entry->entry_dn);
++		}
++		free (uac_str);
++		adcli_attrs_free (attrs);
++	} else {
++		res = ADCLI_SUCCESS;
++	}
++
++	return res;
++}
++
++adcli_result
++adcli_entry_set_passwd (adcli_entry *entry, const char *user_pwd)
++{
++	adcli_result res;
++	LDAP *ldap;
++	krb5_error_code code;
++	krb5_context k5;
++	krb5_ccache ccache;
++	krb5_data result_string = { 0, };
++	krb5_data result_code_string = { 0, };
++	int result_code;
++	char *message;
++	krb5_principal user_principal;
++
++	ldap = adcli_conn_get_ldap_connection (entry->conn);
++	return_unexpected_if_fail (ldap != NULL);
++
++	/* Find the user */
++	res = update_entry_from_domain (entry, ldap);
++	if (res != ADCLI_SUCCESS)
++		return res;
++
++	if (!entry->entry_dn) {
++		_adcli_err ("Cannot find the %s entry %s in the domain",
++		            entry->object_class, entry->sam_name);
++		return ADCLI_ERR_CONFIG;
++	}
++
++	k5 = adcli_conn_get_krb5_context (entry->conn);
++	return_unexpected_if_fail (k5 != NULL);
++
++	code = _adcli_krb5_build_principal (k5, entry->sam_name,
++	                                    adcli_conn_get_domain_realm (entry->conn),
++	                                    &user_principal);
++	return_unexpected_if_fail (code == 0);
++
++	ccache = adcli_conn_get_login_ccache (entry->conn);
++	return_unexpected_if_fail (ccache != NULL);
++
++	memset (&result_string, 0, sizeof (result_string));
++	memset (&result_code_string, 0, sizeof (result_code_string));
++
++	code = krb5_set_password_using_ccache (k5, ccache, user_pwd,
++	                                       user_principal, &result_code,
++	                                       &result_code_string, &result_string);
++
++	if (code != 0) {
++		_adcli_err ("Couldn't set password for %s account: %s: %s",
++		            entry->object_class,
++		            entry->sam_name, krb5_get_error_message (k5, code));
++		/* TODO: Parse out these values */
++		res = ADCLI_ERR_DIRECTORY;
++
++	} else if (result_code != 0) {
++#ifdef HAVE_KRB5_CHPW_MESSAGE
++		if (krb5_chpw_message (k5, &result_string, &message) != 0)
++			message = NULL;
++#else
++		message = NULL;
++		if (result_string.length)
++			message = _adcli_str_dupn (result_string.data, result_string.length);
++#endif
++		_adcli_err ("Cannot set %s password: %.*s%s%s",
++		            entry->object_class,
++		            (int)result_code_string.length, result_code_string.data,
++		            message ? ": " : "", message ? message : "");
++		res = ADCLI_ERR_CREDENTIALS;
++#ifdef HAVE_KRB5_CHPW_MESSAGE
++		krb5_free_string (k5, message);
++#else
++		free (message);
++#endif
++	} else {
++		_adcli_info ("Password (re)setted for %s: %s", entry->object_class, entry->entry_dn);
++
++		res = adcli_entry_ensure_enabled (entry);
++	}
++
++	return res;
++}
++
+ const char *
+ adcli_entry_get_sam_name (adcli_entry *entry)
+ {
+diff --git a/library/adentry.h b/library/adentry.h
+index ae90689..f2382b1 100644
+--- a/library/adentry.h
++++ b/library/adentry.h
+@@ -49,6 +49,9 @@ adcli_result       adcli_entry_modify                   (adcli_entry *entry,
+ 
+ adcli_result       adcli_entry_delete                   (adcli_entry *entry);
+ 
++adcli_result       adcli_entry_set_passwd               (adcli_entry *entry,
++                                                         const char *user_pwd);
++
+ const char *       adcli_entry_get_domain_ou            (adcli_entry *entry);
+ 
+ void               adcli_entry_set_domain_ou            (adcli_entry *entry,
+diff --git a/tools/entry.c b/tools/entry.c
+index 05e4313..52d2546 100644
+--- a/tools/entry.c
++++ b/tools/entry.c
+@@ -24,6 +24,7 @@
+ #include "config.h"
+ 
+ #include "adcli.h"
++#include "adprivate.h"
+ #include "adattrs.h"
+ #include "tools.h"
+ 
+@@ -385,6 +386,104 @@ adcli_tool_user_delete (adcli_conn *conn,
+ 	return 0;
+ }
+ 
++int
++adcli_tool_user_passwd (adcli_conn *conn,
++                        int argc,
++                        char *argv[])
++{
++	adcli_result res;
++	adcli_entry *entry;
++	int opt;
++	char *user_pwd = NULL;
++
++	struct option options[] = {
++		{ "domain", required_argument, NULL, opt_domain },
++		{ "domain-realm", required_argument, NULL, opt_domain_realm },
++		{ "domain-controller", required_argument, NULL, opt_domain_controller },
++		{ "use-ldaps", no_argument, 0, opt_use_ldaps },
++		{ "login-user", required_argument, NULL, opt_login_user },
++		{ "login-ccache", optional_argument, NULL, opt_login_ccache },
++		{ "no-password", no_argument, 0, opt_no_password },
++		{ "stdin-password", no_argument, 0, opt_stdin_password },
++		{ "prompt-password", no_argument, 0, opt_prompt_password },
++		{ "verbose", no_argument, NULL, opt_verbose },
++		{ "help", no_argument, NULL, 'h' },
++		{ 0 },
++	};
++
++	static adcli_tool_desc usages[] = {
++		{ 0, "usage: adcli passwd-user --domain=xxxx user" },
++		{ 0 },
++	};
++
++	while ((opt = adcli_tool_getopt (argc, argv, options)) != -1) {
++		switch (opt) {
++		case 'h':
++		case '?':
++		case ':':
++			adcli_tool_usage (options, usages);
++			adcli_tool_usage (options, common_usages);
++			return opt == 'h' ? 0 : 2;
++		default:
++			res = parse_option ((Option)opt, optarg, conn);
++			if (res != ADCLI_SUCCESS) {
++				return res;
++			}
++			break;
++		}
++	}
++
++	argc -= optind;
++	argv += optind;
++
++	if (argc != 1) {
++		warnx ("specify one user name to (re)set password");
++		return 2;
++	}
++
++	entry = adcli_entry_new_user (conn, argv[0]);
++	if (entry == NULL) {
++		warnx ("unexpected memory problems");
++		return -1;
++	}
++
++	adcli_conn_set_allowed_login_types (conn, ADCLI_LOGIN_USER_ACCOUNT);
++
++	res = adcli_conn_connect (conn);
++	if (res != ADCLI_SUCCESS) {
++		warnx ("couldn't connect to %s domain: %s",
++		       adcli_conn_get_domain_name (conn),
++		       adcli_get_last_error ());
++		adcli_entry_unref (entry);
++		return -res;
++	}
++
++	user_pwd = adcli_prompt_password_func (ADCLI_LOGIN_USER_ACCOUNT,
++	                                       adcli_entry_get_sam_name(entry),
++	                                       0, NULL);
++	if (user_pwd == NULL || *user_pwd == '\0') {
++		warnx ("missing password");
++		_adcli_password_free (user_pwd);
++		adcli_entry_unref (entry);
++		return 2;
++	}
++
++	res = adcli_entry_set_passwd (entry, user_pwd);
++	_adcli_password_free (user_pwd);
++	if (res != ADCLI_SUCCESS) {
++		warnx ("(re)setting password for user %s in domain %s failed: %s",
++		       adcli_entry_get_sam_name (entry),
++		       adcli_conn_get_domain_name (conn),
++		       adcli_get_last_error ());
++		adcli_entry_unref (entry);
++		return -res;
++	}
++
++	adcli_entry_unref (entry);
++
++	return 0;
++}
++
+ int
+ adcli_tool_group_create (adcli_conn *conn,
+                          int argc,
+diff --git a/tools/tools.c b/tools/tools.c
+index 84bbba9..a14b9ca 100644
+--- a/tools/tools.c
++++ b/tools/tools.c
+@@ -63,6 +63,7 @@ struct {
+ 	{ "create-msa", adcli_tool_computer_managed_service_account, "Create a managed service account in the given AD domain", },
+ 	{ "create-user", adcli_tool_user_create, "Create a user account", },
+ 	{ "delete-user", adcli_tool_user_delete, "Delete a user account", },
++	{ "passwd-user", adcli_tool_user_passwd, "(Re)set a user password", },
+ 	{ "create-group", adcli_tool_group_create, "Create a group", },
+ 	{ "delete-group", adcli_tool_group_delete, "Delete a group", },
+ 	{ "add-member", adcli_tool_member_add, "Add users to a group", },
+diff --git a/tools/tools.h b/tools/tools.h
+index 82d5e4e..d38aa32 100644
+--- a/tools/tools.h
++++ b/tools/tools.h
+@@ -94,6 +94,10 @@ int       adcli_tool_user_delete       (adcli_conn *conn,
+                                         int argc,
+                                         char *argv[]);
+ 
++int       adcli_tool_user_passwd       (adcli_conn *conn,
++                                        int argc,
++                                        char *argv[]);
++
+ int       adcli_tool_group_create      (adcli_conn *conn,
+                                         int argc,
+                                         char *argv[]);
+-- 
+2.31.1
+
diff --git a/SOURCES/0004-enroll-more-filters-for-random-characters.patch b/SOURCES/0004-enroll-more-filters-for-random-characters.patch
new file mode 100644
index 0000000000000000000000000000000000000000..911e229978979db8a2989d1991feba888f5fc72e
--- /dev/null
+++ b/SOURCES/0004-enroll-more-filters-for-random-characters.patch
@@ -0,0 +1,77 @@
+From 2750f536ac6746756335eec8332060d2365a4126 Mon Sep 17 00:00:00 2001
+From: Sumit Bose <sbose@redhat.com>
+Date: Tue, 27 Oct 2020 14:44:07 +0100
+Subject: [PATCH 4/7] enroll: more filters for random characters
+
+Make handling of random strings more flexible.
+
+Resolves: https://bugzilla.redhat.com/show_bug.cgi?id=1854112
+---
+ library/adenroll.c | 30 +++++++++++++++++++++++++++---
+ 1 file changed, 27 insertions(+), 3 deletions(-)
+
+diff --git a/library/adenroll.c b/library/adenroll.c
+index 9cdc79b..44383cc 100644
+--- a/library/adenroll.c
++++ b/library/adenroll.c
+@@ -259,6 +259,29 @@ ensure_computer_sam (adcli_result res,
+ 	return ADCLI_SUCCESS;
+ }
+ 
++typedef int (rand_filter) (char *password, int length);
++
++static int
++filter_sam_chars (char *password,
++                       int length)
++{
++	int i, j;
++
++	/*
++	 * There are a couple of restrictions for characters in the
++	 * sAMAccountName attribute value, for our purpose (random suffix)
++	 * letters and numbers are sufficient.
++	 */
++	for (i = 0, j = 0; i < length; i++) {
++		if (password[i] >= 48 && password[i] <= 122 &&
++		    isalnum (password[i]))
++			password[j++] = password[i];
++	}
++
++	/* return the number of valid characters remaining */
++	return j;
++}
++
+ static int
+ filter_password_chars (char *password,
+                        int length)
+@@ -283,7 +306,8 @@ filter_password_chars (char *password,
+ 
+ static char *
+ generate_host_password  (adcli_enroll *enroll,
+-                         size_t length)
++                         size_t length,
++                         rand_filter *filter)
+ {
+ 	char *password;
+ 	krb5_context k5;
+@@ -305,7 +329,7 @@ generate_host_password  (adcli_enroll *enroll,
+ 		code = krb5_c_random_make_octets (k5, &buffer);
+ 		return_val_if_fail (code == 0, NULL);
+ 
+-		at += filter_password_chars (buffer.data, buffer.length);
++		at += filter (buffer.data, buffer.length);
+ 		assert (at <= length);
+ 	}
+ 
+@@ -333,7 +357,7 @@ ensure_computer_password (adcli_result res,
+ 		_adcli_info ("Using default reset computer password");
+ 
+ 	} else {
+-		enroll->computer_password = generate_host_password (enroll, length);
++		enroll->computer_password = generate_host_password (enroll, length, filter_password_chars);
+ 		return_unexpected_if_fail (enroll->computer_password != NULL);
+ 		_adcli_info ("Generated %d character computer password", length);
+ 	}
+-- 
+2.28.0
+
diff --git a/SOURCES/0005-enroll-make-adcli_enroll_add_keytab_for_service_acco.patch b/SOURCES/0005-enroll-make-adcli_enroll_add_keytab_for_service_acco.patch
new file mode 100644
index 0000000000000000000000000000000000000000..4da2d9dc8204d57e9a9620e0c65ab1887a9ebc69
--- /dev/null
+++ b/SOURCES/0005-enroll-make-adcli_enroll_add_keytab_for_service_acco.patch
@@ -0,0 +1,91 @@
+From 81c98e367ba4bc8d77668acd31e462ad31cf12be Mon Sep 17 00:00:00 2001
+From: Sumit Bose <sbose@redhat.com>
+Date: Tue, 27 Oct 2020 14:47:31 +0100
+Subject: [PATCH 5/7] enroll: make adcli_enroll_add_keytab_for_service_account
+ public
+
+Determine keytab name more early to catch errors more early.
+
+Resolves: https://bugzilla.redhat.com/show_bug.cgi?id=1854112
+---
+ library/adenroll.c | 13 +++++++------
+ library/adenroll.h |  2 ++
+ tools/computer.c   |  6 ++++++
+ 3 files changed, 15 insertions(+), 6 deletions(-)
+
+diff --git a/library/adenroll.c b/library/adenroll.c
+index 44383cc..05bb085 100644
+--- a/library/adenroll.c
++++ b/library/adenroll.c
+@@ -2276,9 +2276,10 @@ adcli_enroll_add_description_for_service_account (adcli_enroll *enroll)
+ 	return ADCLI_SUCCESS;
+ }
+ 
+-static adcli_result
++adcli_result
+ adcli_enroll_add_keytab_for_service_account (adcli_enroll *enroll)
+ {
++	adcli_result res;
+ 	krb5_context k5;
+ 	krb5_error_code code;
+ 	char def_keytab_name[MAX_KEYTAB_NAME_LEN];
+@@ -2286,11 +2287,14 @@ adcli_enroll_add_keytab_for_service_account (adcli_enroll *enroll)
+ 	int ret;
+ 
+ 	if (adcli_enroll_get_keytab_name (enroll) == NULL) {
+-		k5 = adcli_conn_get_krb5_context (enroll->conn);
+-		return_unexpected_if_fail (k5 != NULL);
++		res = _adcli_krb5_init_context (&k5);
++		if (res != ADCLI_SUCCESS) {
++			return res;
++		}
+ 
+ 		code = krb5_kt_default_name (k5, def_keytab_name,
+ 		                             sizeof (def_keytab_name));
++		krb5_free_context (k5);
+ 		return_unexpected_if_fail (code == 0);
+ 
+ 		lc_dom_name = strdup (adcli_conn_get_domain_name (enroll->conn));
+@@ -2326,9 +2330,6 @@ adcli_enroll_join (adcli_enroll *enroll,
+ 
+ 	if (enroll->is_service) {
+ 		res = adcli_enroll_add_description_for_service_account (enroll);
+-		if (res == ADCLI_SUCCESS) {
+-			res = adcli_enroll_add_keytab_for_service_account (enroll);
+-		}
+ 	} else {
+ 		res = ensure_default_service_names (enroll);
+ 	}
+diff --git a/library/adenroll.h b/library/adenroll.h
+index 7765ed4..11a30c8 100644
+--- a/library/adenroll.h
++++ b/library/adenroll.h
+@@ -146,6 +146,8 @@ const char *       adcli_enroll_get_keytab_name         (adcli_enroll *enroll);
+ void               adcli_enroll_set_keytab_name         (adcli_enroll *enroll,
+                                                          const char *value);
+ 
++adcli_result       adcli_enroll_add_keytab_for_service_account (adcli_enroll *enroll);
++
+ krb5_enctype *     adcli_enroll_get_keytab_enctypes     (adcli_enroll *enroll);
+ 
+ void               adcli_enroll_set_keytab_enctypes     (adcli_enroll *enroll,
+diff --git a/tools/computer.c b/tools/computer.c
+index 63fd374..98a0472 100644
+--- a/tools/computer.c
++++ b/tools/computer.c
+@@ -1166,6 +1166,12 @@ adcli_tool_computer_managed_service_account (adcli_conn *conn,
+ 
+ 	adcli_enroll_set_is_service (enroll, true);
+ 	adcli_conn_set_allowed_login_types (conn, ADCLI_LOGIN_USER_ACCOUNT);
++	res = adcli_enroll_add_keytab_for_service_account (enroll);
++	if (res != ADCLI_SUCCESS) {
++		warnx ("Failed to set domain specific keytab name");
++		adcli_enroll_unref (enroll);
++		return 2;
++	}
+ 
+ 	res = adcli_enroll_load (enroll);
+ 	if (res != ADCLI_SUCCESS) {
+-- 
+2.28.0
+
diff --git a/SOURCES/0006-enroll-allow-fqdn-for-locate_computer_account.patch b/SOURCES/0006-enroll-allow-fqdn-for-locate_computer_account.patch
new file mode 100644
index 0000000000000000000000000000000000000000..9f6094ac076ddb355680c0a5f4baf8e92b165020
--- /dev/null
+++ b/SOURCES/0006-enroll-allow-fqdn-for-locate_computer_account.patch
@@ -0,0 +1,129 @@
+From 2a695dfe09cafeee3a648d3b969c364f8d3f494f Mon Sep 17 00:00:00 2001
+From: Sumit Bose <sbose@redhat.com>
+Date: Tue, 27 Oct 2020 14:49:55 +0100
+Subject: [PATCH 6/7] enroll: allow fqdn for locate_computer_account
+
+Make it possible to find existing manages service account by the
+fully-qualified name.
+
+Resolves: https://bugzilla.redhat.com/show_bug.cgi?id=1854112
+---
+ library/adenroll.c | 45 +++++++++++++++++++++++++++++++--------------
+ 1 file changed, 31 insertions(+), 14 deletions(-)
+
+diff --git a/library/adenroll.c b/library/adenroll.c
+index 05bb085..98cd5fa 100644
+--- a/library/adenroll.c
++++ b/library/adenroll.c
+@@ -990,10 +990,11 @@ delete_computer_account (adcli_enroll *enroll,
+ static adcli_result
+ locate_computer_account (adcli_enroll *enroll,
+                          LDAP *ldap,
++                         bool use_fqdn,
+                          LDAPMessage **rresults,
+                          LDAPMessage **rentry)
+ {
+-	char *attrs[] = { "objectClass", NULL };
++	char *attrs[] = { "objectClass", "CN", NULL };
+ 	LDAPMessage *results = NULL;
+ 	LDAPMessage *entry = NULL;
+ 	const char *base;
+@@ -1003,12 +1004,22 @@ locate_computer_account (adcli_enroll *enroll,
+ 	int ret = 0;
+ 
+ 	/* If we don't yet know our computer dn, then try and find it */
+-	value = _adcli_ldap_escape_filter (enroll->computer_sam);
+-	return_unexpected_if_fail (value != NULL);
+-	if (asprintf (&filter, "(&(objectClass=%s)(sAMAccountName=%s))",
+-	              enroll->is_service ? "msDS-ManagedServiceAccount" : "computer",
+-	              value) < 0)
+-		return_unexpected_if_reached ();
++	if (use_fqdn) {
++		return_unexpected_if_fail (enroll->host_fqdn != NULL);
++		value = _adcli_ldap_escape_filter (enroll->host_fqdn);
++		return_unexpected_if_fail (value != NULL);
++		if (asprintf (&filter, "(&(objectClass=%s)(dNSHostName=%s))",
++		              enroll->is_service ? "msDS-ManagedServiceAccount" : "computer",
++		              value) < 0)
++			return_unexpected_if_reached ();
++	} else {
++		value = _adcli_ldap_escape_filter (enroll->computer_sam);
++		return_unexpected_if_fail (value != NULL);
++		if (asprintf (&filter, "(&(objectClass=%s)(sAMAccountName=%s))",
++		              enroll->is_service ? "msDS-ManagedServiceAccount" : "computer",
++		              value) < 0)
++			return_unexpected_if_reached ();
++	}
+ 	free (value);
+ 
+ 	base = adcli_conn_get_default_naming_context (enroll->conn);
+@@ -1031,21 +1042,26 @@ locate_computer_account (adcli_enroll *enroll,
+ 			enroll->computer_dn = strdup (dn);
+ 			return_unexpected_if_fail (enroll->computer_dn != NULL);
+ 			_adcli_info ("Found %s account for %s at: %s",
+-			             s_or_c (enroll), enroll->computer_sam, dn);
++			             s_or_c (enroll),
++			             use_fqdn ? enroll->host_fqdn
++			                      : enroll->computer_sam, dn);
+ 			ldap_memfree (dn);
+ 
+ 		} else {
+ 			ldap_msgfree (results);
+ 			results = NULL;
+ 			_adcli_info ("A %s account for %s does not exist",
+-			             s_or_c (enroll), enroll->computer_sam);
++			             s_or_c (enroll),
++			             use_fqdn ? enroll->host_fqdn
++			                      : enroll->computer_sam);
+ 		}
+ 
+ 	} else {
+ 		return _adcli_ldap_handle_failure (ldap, ADCLI_ERR_DIRECTORY,
+ 		                                   "Couldn't lookup %s account: %s",
+ 		                                   s_or_c (enroll),
+-		                                   enroll->computer_sam);
++		                                   use_fqdn ? enroll->host_fqdn
++		                                            :enroll->computer_sam);
+ 	}
+ 
+ 	if (rresults)
+@@ -1120,7 +1136,8 @@ locate_or_create_computer_account (adcli_enroll *enroll,
+ 
+ 	/* Try to find the computer account */
+ 	if (!enroll->computer_dn) {
+-		res = locate_computer_account (enroll, ldap, &results, &entry);
++		res = locate_computer_account (enroll, ldap, false,
++		                               &results, &entry);
+ 		if (res != ADCLI_SUCCESS)
+ 			return res;
+ 		searched = 1;
+@@ -2395,7 +2412,7 @@ adcli_enroll_read_computer_account (adcli_enroll *enroll,
+ 
+ 	/* Find the computer dn */
+ 	if (!enroll->computer_dn) {
+-		res = locate_computer_account (enroll, ldap, NULL, NULL);
++		res = locate_computer_account (enroll, ldap, false, NULL, NULL);
+ 		if (res != ADCLI_SUCCESS)
+ 			return res;
+ 		if (!enroll->computer_dn) {
+@@ -2508,7 +2525,7 @@ adcli_enroll_delete (adcli_enroll *enroll,
+ 
+ 	/* Find the computer dn */
+ 	if (!enroll->computer_dn) {
+-		res = locate_computer_account (enroll, ldap, NULL, NULL);
++		res = locate_computer_account (enroll, ldap, false, NULL, NULL);
+ 		if (res != ADCLI_SUCCESS)
+ 			return res;
+ 		if (!enroll->computer_dn) {
+@@ -2552,7 +2569,7 @@ adcli_enroll_password (adcli_enroll *enroll,
+ 
+ 	/* Find the computer dn */
+ 	if (!enroll->computer_dn) {
+-		res = locate_computer_account (enroll, ldap, NULL, NULL);
++		res = locate_computer_account (enroll, ldap, false, NULL, NULL);
+ 		if (res != ADCLI_SUCCESS)
+ 			return res;
+ 		if (!enroll->computer_dn) {
+-- 
+2.28.0
+
diff --git a/SOURCES/0007-service-account-add-random-suffix-to-account-name.patch b/SOURCES/0007-service-account-add-random-suffix-to-account-name.patch
new file mode 100644
index 0000000000000000000000000000000000000000..d2635cae60247fb3c55a67b2bebb45af07537711
--- /dev/null
+++ b/SOURCES/0007-service-account-add-random-suffix-to-account-name.patch
@@ -0,0 +1,122 @@
+From 6b94f9712378b8f1fa1bc530c64cb987abb0c43b Mon Sep 17 00:00:00 2001
+From: Sumit Bose <sbose@redhat.com>
+Date: Tue, 27 Oct 2020 15:23:04 +0100
+Subject: [PATCH 7/7] service-account: add random suffix to account name
+
+Add a random component to the default managed service account name to
+avoid name collisions.
+
+Resolves: https://bugzilla.redhat.com/show_bug.cgi?id=1854112
+---
+ library/adenroll.c | 79 ++++++++++++++++++++++++++++++++++++++++++++++
+ 1 file changed, 79 insertions(+)
+
+diff --git a/library/adenroll.c b/library/adenroll.c
+index 98cd5fa..f693e58 100644
+--- a/library/adenroll.c
++++ b/library/adenroll.c
+@@ -1121,6 +1121,59 @@ load_computer_account (adcli_enroll *enroll,
+ 	return ADCLI_SUCCESS;
+ }
+ 
++static adcli_result
++refresh_service_account_name_sam_and_princ (adcli_enroll *enroll,
++                                            const char *name)
++{
++	adcli_result res;
++
++	adcli_enroll_set_computer_name (enroll, name);
++	res = ensure_computer_sam (ADCLI_SUCCESS, enroll);
++	res = ensure_keytab_principals (res, enroll);
++
++	return res;
++}
++
++static adcli_result
++calculate_random_service_account_name (adcli_enroll *enroll)
++{
++	char *suffix;
++	char *new_name;
++	int ret;
++	adcli_result res;
++
++	suffix = generate_host_password (enroll, 3, filter_sam_chars);
++	return_unexpected_if_fail (suffix != NULL);
++
++	ret = asprintf (&new_name, "%s!%s", enroll->computer_name, suffix);
++	free (suffix);
++	return_unexpected_if_fail (ret > 0);
++
++	res = refresh_service_account_name_sam_and_princ (enroll, new_name);
++	free (new_name);
++
++	return res;
++}
++
++static adcli_result
++get_service_account_name_from_ldap (adcli_enroll *enroll, LDAPMessage *results)
++{
++	LDAP *ldap;
++	char *cn;
++	adcli_result res;
++
++	ldap = adcli_conn_get_ldap_connection (enroll->conn);
++	assert (ldap != NULL);
++
++	cn = _adcli_ldap_parse_value (ldap, results, "CN");
++	return_unexpected_if_fail (cn != NULL);
++
++	res = refresh_service_account_name_sam_and_princ (enroll, cn);
++	free (cn);
++
++	return res;
++}
++
+ static adcli_result
+ locate_or_create_computer_account (adcli_enroll *enroll,
+                                    int allow_overwrite)
+@@ -1143,8 +1196,32 @@ locate_or_create_computer_account (adcli_enroll *enroll,
+ 		searched = 1;
+ 	}
+ 
++	/* Try with fqdn for service accounts */
++	if (!enroll->computer_dn && enroll->is_service
++	                && enroll->host_fqdn != NULL) {
++		res = locate_computer_account (enroll, ldap, true,
++		                               &results, &entry);
++		if (res != ADCLI_SUCCESS)
++			return res;
++		searched = 1;
++
++		if (results != NULL) {
++			res = get_service_account_name_from_ldap (enroll,
++			                                          results);
++			if (res != ADCLI_SUCCESS) {
++				return res;
++			}
++		}
++	}
++
+ 	/* Next try and come up with where we think it should be */
+ 	if (enroll->computer_dn == NULL) {
++		if (enroll->is_service && !enroll->computer_name_explicit) {
++			res = calculate_random_service_account_name (enroll);
++			if (res != ADCLI_SUCCESS) {
++				return res;
++			}
++		}
+ 		res = calculate_computer_account (enroll, ldap);
+ 		if (res != ADCLI_SUCCESS)
+ 			return res;
+@@ -2113,6 +2190,8 @@ adcli_enroll_prepare (adcli_enroll *enroll,
+ 
+ 	if (enroll->is_service) {
+ 		/* Ensure basic params for service accounts */
++		res = ensure_host_fqdn (res, enroll);
++		res = ensure_computer_name (res, enroll);
+ 		res = ensure_computer_sam (res, enroll);
+ 		res = ensure_computer_password (res, enroll);
+ 		res = ensure_host_keytab (res, enroll);
+-- 
+2.28.0
+
diff --git a/SPECS/adcli.spec b/SPECS/adcli.spec
index d0ecaa3c7641400b00abe06b001c22a53a9d2a6a..3fc795c69d488895b506b2bc3c7433e13ec3febc 100644
--- a/SPECS/adcli.spec
+++ b/SPECS/adcli.spec
@@ -1,6 +1,6 @@
 Name:		adcli
 Version:	0.8.2
-Release:	6%{?dist}
+Release:	12%{?dist}
 Summary:	Active Directory enrollment
 License:	LGPLv2+
 URL:		http://cgit.freedesktop.org/realmd/adcli
@@ -115,7 +115,7 @@ Patch64:	0001-discovery-fix.patch
 Patch65:	0001-delete-do-not-exit-if-keytab-cannot-be-read.patch
 
 # rhbz#1846878 - adcli: presetting $computer in $domain domain failed: Cannot
-#               set computer password: Authentication error
+# set computer password: Authentication error
 Patch66:	0001-tools-disable-SSSD-s-locator-plugin.patch
 
 # rhbz#1791611 - Typo in adcli update --help option
@@ -123,6 +123,50 @@ Patch67:	0001-tools-fix-typo-in-show-password-help-output.patch
 
 # rhbz#1791545 - Manpage and help does not explain the use of "-C" option
 Patch68:	0001-man-explain-optional-parameter-of-login-ccache-bette.patch
+Patch69:	0001-man-make-handling-of-optional-credential-cache-more-.patch
+
+# rhbz#1883467 - Add --use-ldaps option to adcli update as well
+Patch70:	0001-tools-add-missing-use-ldaps-option-to-update-and-tes.patch
+
+# rhbz#1734764 - Cannot join a pre-staged Computer Account on AD in Custom OU
+# using Delegated user
+Patch71:	0001-join-update-set-dNSHostName-if-not-set.patch
+
+# rhbz#1852080 - missing documentation for required AD rights for adcli join
+# and net join
+Patch72:	0001-doc-add-missing-samba_data_tool_path.xml-.in-to-EXTR.patch
+Patch73:	0001-doc-explain-required-AD-permissions.patch
+
+# rhbz#1854112 - [RFE] Add new mode to just create an AD account to be able to
+# connect to LDAP
+Patch74:	0001-enroll-add-is_service-member.patch
+Patch75:	0002-computer-add-create-msa-sub-command.patch
+Patch76:	0003-enroll-use-computer-or-service-in-debug-messages.patch
+Patch77:	0004-enroll-more-filters-for-random-characters.patch
+Patch78:	0005-enroll-make-adcli_enroll_add_keytab_for_service_acco.patch
+Patch79:	0006-enroll-allow-fqdn-for-locate_computer_account.patch
+Patch80:	0007-service-account-add-random-suffix-to-account-name.patch
+
+# rhbz#1906303 - Typo in CREATE A SERVICE ACCOUNT section of man page of adcli
+Patch81:	0001-service-account-fix-typo-in-the-man-page-entry.patch
+
+# rhbz#1889386 - [RFE] Adcli and Realm Error Code Optimization Request
+Patch82:	0001-build-add-with-vendor-error-message-configure-option.patch
+
+# rhbz#1769644 - [RFE] adcli should allow to modify DONT_EXPIRE_PASSWORD attribute
+Patch83:	0001-coverity-add-missing-NULL-checks.patch
+Patch84:	0002-Add-dont-expire-password-option.patch
+Patch85:	0001-Fix-for-dont-expire-password-option-and-join.patch
+
+# rhbz#1952828 - [RFE] Allow adcli to create AD user with password as well as
+# set or reset existing user password
+Patch86:	0001-library-move-UAC-flags-to-a-more-common-header-file.patch
+Patch87:	0002-adcli_entry-add-entry_attrs-with-userAccountControl-.patch
+Patch88:	0003-entry-add-passwd-user-sub-command.patch
+
+# rhbz#1690920 - [RFE] add option to populate "managed by" computer attribute
+Patch89:	0001-Add-setattr-option.patch
+Patch90:	0002-Add-delattr-option.patch
 
 BuildRequires:	gcc
 BuildRequires:	intltool pkgconfig
@@ -150,7 +194,11 @@ standard LDAP and Kerberos calls.
 
 %build
 autoreconf --force --install --verbose
-%configure --disable-static --disable-silent-rules
+%configure --disable-static --disable-silent-rules \
+%if 0%{?rhel}
+    --with-vendor-error-message='Please check\n    https://red.ht/support_rhel_ad \nto get help for common issues.' \
+%endif
+    %{nil}
 make %{?_smp_mflags}
 
 %check
@@ -184,6 +232,33 @@ documentation.
 %doc %{_datadir}/doc/adcli/*
 
 %changelog
+* Mon Jun 14 2021 Sumit Bose <sbose@redhat.com> - 0.8.2-12
+- [RFE] Allow adcli to create AD user with password as well as set or reset
+  existing user password [#1952828]
+- [RFE] add option to populate "managed by" computer attribute [#1690920]
+
+* Thu Jun 03 2021 Sumit Bose <sbose@redhat.com> - 0.8.2-11
+- Add missing patch for [#1769644]
+
+* Thu Jun 03 2021 Sumit Bose <sbose@redhat.com> - 0.8.2-10
+- [RFE] Adcli and Realm Error Code Optimization Request [#1889386]
+- [RFE] adcli should allow to modify DONT_EXPIRE_PASSWORD attribute [#1769644]
+
+* Fri Dec 11 2020 Sumit Bose <sbose@redhat,com> - 0.8.2-9
+- Typo in CREATE A SERVICE ACCOUNT section of man page of adcli [#1906303]
+
+* Wed Nov 11 2020 Sumit Bose <sbose@redhat.com> - 0.8.2-8
+- Add --use-ldaps option to adcli update as well [#1883467]
+- Cannot join a pre-staged Computer Account on AD in Custom OU using Delegated
+  user [#1734764]
+- missing documentation for required AD rights for adcli join and net
+  join [#1852080]
+- [RFE] Add new mode to just create an AD account to be able to connect to
+  LDAP [#1854112]
+
+* Thu Aug 13 2020 Sumit Bose <sbose@redhat.com> - 0.8.2-7
+- Improve "-C" option description in man page even more [#1791545]
+
 * Mon Jun 15 2020 Sumit Bose <sbose@redhat.com> - 0.8.2-6
 - [abrt] [faf] adcli: raise(): /usr/sbin/adcli killed by 6 [#1806260]
 - No longer able to delete computer from AD using adcli [#1846882]