New Upstream Snapshot - adcli

Ready changes

Summary

Merged new upstream version: 0.9.2 (was: 0.9.1+git20220927.1.8183e45).

Resulting package

Built on 2022-12-17T12:22 (took 7m29s)

The resulting binary packages can be installed (if you have the apt repository enabled) by running one of:

apt install -t fresh-snapshots adcli-dbgsymapt install -t fresh-snapshots adcli

Lintian Result

Diff

diff --git a/.gitignore b/.gitignore
deleted file mode 100644
index c64f4e7..0000000
--- a/.gitignore
+++ /dev/null
@@ -1,61 +0,0 @@
-*~
-*.la
-*.lo
-*.o
-*.tar.gz
-*.tar.gz.sig
-*.gcno
-*.gcda
-*.log
-*.plist
-*.trs
-
-.cproject
-.deps
-.libs
-.project
-Makefile
-Makefile.in
-
-/ABOUT-NLS
-/INSTALL
-/aclocal.m4
-/autom4te.cache
-/compile
-/config.guess
-/config.h
-/config.h.in
-/config.log
-/config.rpath
-/config.status
-/config.sub
-/configure
-/depcomp
-/install-sh
-/libtool
-/ltmain.sh
-/missing
-/stamp-h1
-/test-driver
-/*.tar.gz
-
-/build/coverage
-/build/coverage.info
-/build/m4/*.m4
-
-/doc/adcli.8
-/doc/html/
-/doc/version.xml
-/doc/samba_data_tool_path.xml
-
-/po/POTFILES
-/po/stamp-po
-
-/library/adcli-1.pc
-/library/test-attrs
-/library/test-ldap
-/library/test-seq
-/library/test-util
-
-/tools/ad-enroll
-/tools/adcli
diff --git a/configure.ac b/configure.ac
index baa0d3b..af62507 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1,7 +1,7 @@
-AC_PREREQ(2.61)
+AC_PREREQ([2.61])
 
 AC_INIT([adcli],
-	[0.9.1],
+	[0.9.2],
 	[https://gitlab.freedesktop.org/realmd/adcli/issues/new],
 	[adcli],
 	[https://gitlab.freedesktop.org/realmd/adcli])
@@ -33,7 +33,7 @@ LT_INIT([dlopen disable-static])
 AC_PROG_CC
 AC_PROG_CPP
 AM_PROG_CC_C_O
-AM_PROG_LIBTOOL
+LT_INIT
 
 # -------------------------------------------------------------------
 # Kerberos
@@ -98,13 +98,15 @@ AC_SUBST(LDAP_CFLAGS)
 # -------------------------------------------------------------------
 # resolv
 
-AC_MSG_CHECKING(for which library has res_query)
+AC_MSG_CHECKING([for which library has res_query, ns_get16 and ns_get32])
 for lib in "" "-lresolv"; do
 	saved_LIBS="$LIBS"
 	LIBS="$LIBS $lib"
 	AC_LINK_IFELSE([
 		AC_LANG_PROGRAM([#include <resolv.h>],
-		                [res_query (0, 0, 0, 0, 0)])
+		                [res_query (0, 0, 0, 0, 0);
+		                 ns_get32 (NULL);
+		                 ns_get16 (NULL);])
 	],
 	[ AC_MSG_RESULT(${lib:-libc}); have_res_query="yes"; break; ],
 	[ LIBS="$saved_LIBS" ])
@@ -123,12 +125,27 @@ 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
 
 AC_MSG_CHECKING([whether to build documentation])
 AC_ARG_ENABLE(doc,
-              AC_HELP_STRING([--enable-doc],
+              AS_HELP_STRING([--enable-doc],
                              [Disable building documentation])
              )
 
@@ -165,7 +182,7 @@ doc_status=$enable_doc
 
 AC_MSG_CHECKING([for debug mode])
 AC_ARG_ENABLE(debug,
-              AC_HELP_STRING([--enable-debug=no/default/yes],
+              AS_HELP_STRING([--enable-debug=no/default/yes],
               [Turn on or off debugging]))
 
 if test "$enable_debug" != "no"; then
@@ -293,7 +310,7 @@ fi
 
 AC_MSG_CHECKING([where is Samba's net utility])
 AC_ARG_WITH([samba_data_tool],
-              AC_HELP_STRING([--with-samba-data-tool=/path],
+              AS_HELP_STRING([--with-samba-data-tool=/path],
               [Path to Samba's net utility]),
               [],
               [with_samba_data_tool=/usr/bin/net])
diff --git a/debian/changelog b/debian/changelog
index 129d88c..e344d8f 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,12 @@
+adcli (0.9.2-1) UNRELEASED; urgency=low
+
+  * New upstream snapshot.
+  * Drop patch 0001-configure-check-for-ns_get16-and-ns_get32-as-well.patch,
+    present upstream.
+  * New upstream release.
+
+ -- Debian Janitor <janitor@jelmer.uk>  Sat, 17 Dec 2022 12:18:00 -0000
+
 adcli (0.9.1-2) unstable; urgency=medium
 
   * d/p: Fix FTBFS with newer versions of glibc res_query() (Closes: #1017150)
diff --git a/debian/patches/0001-configure-check-for-ns_get16-and-ns_get32-as-well.patch b/debian/patches/0001-configure-check-for-ns_get16-and-ns_get32-as-well.patch
deleted file mode 100644
index 0121053..0000000
--- a/debian/patches/0001-configure-check-for-ns_get16-and-ns_get32-as-well.patch
+++ /dev/null
@@ -1,34 +0,0 @@
-From: Sumit Bose <sbose@redhat.com>
-Date: Wed, 28 Jul 2021 12:55:16 +0200
-Subject: configure: check for ns_get16 and ns_get32 as well
-
-With newer versions of glibc res_query() might ba already available in
-glibc with ns_get16() and ns_get32() still requires libresolv.
-
-Resolves: https://bugzilla.redhat.com/show_bug.cgi?id=1984891
----
- configure.ac | 6 ++++--
- 1 file changed, 4 insertions(+), 2 deletions(-)
-
-diff --git a/configure.ac b/configure.ac
-index baa0d3b..a166804 100644
---- a/configure.ac
-+++ b/configure.ac
-@@ -98,13 +98,15 @@ AC_SUBST(LDAP_CFLAGS)
- # -------------------------------------------------------------------
- # resolv
- 
--AC_MSG_CHECKING(for which library has res_query)
-+AC_MSG_CHECKING([for which library has res_query, ns_get16 and ns_get32])
- for lib in "" "-lresolv"; do
- 	saved_LIBS="$LIBS"
- 	LIBS="$LIBS $lib"
- 	AC_LINK_IFELSE([
- 		AC_LANG_PROGRAM([#include <resolv.h>],
--		                [res_query (0, 0, 0, 0, 0)])
-+		                [res_query (0, 0, 0, 0, 0);
-+		                 ns_get32 (NULL);
-+		                 ns_get16 (NULL);])
- 	],
- 	[ AC_MSG_RESULT(${lib:-libc}); have_res_query="yes"; break; ],
- 	[ LIBS="$saved_LIBS" ])
diff --git a/debian/patches/series b/debian/patches/series
index d37da2c..e69de29 100644
--- a/debian/patches/series
+++ b/debian/patches/series
@@ -1 +0,0 @@
-0001-configure-check-for-ns_get16-and-ns_get32-as-well.patch
diff --git a/doc/Makefile.am b/doc/Makefile.am
index 50fb777..410e42a 100644
--- a/doc/Makefile.am
+++ b/doc/Makefile.am
@@ -53,9 +53,10 @@ XSLTPROC_FLAGS = \
 XSLTPROC_MAN = \
 	$(XSLTPROC) $(XSLTPROC_FLAGS) http://docbook.sourceforge.net/release/xsl/current/manpages/docbook.xsl
 
-permissions.xml: ../library/adenroll.c adcli.xml
+permissions.xml: ../library/adenroll.c
 	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' >> $@
+	sed -n -e 's#.*"\(.*\)".* /\* :ADPermissions: \(.*\) \*/$$#<listitem><para>\1</para><itemizedlist><listitem><para>\2</para></listitem></itemizedlist></listitem>#p' $? \
+	    | sed -e 's# *\* *#</para></listitem><listitem><para>#g' >> $@
 	echo "</itemizedlist>" >> $@
 
 $(man8_MANS): permissions.xml
diff --git a/doc/adcli-devel.xml b/doc/adcli-devel.xml
index dcf3f94..0f1f4ad 100644
--- a/doc/adcli-devel.xml
+++ b/doc/adcli-devel.xml
@@ -93,7 +93,7 @@ $ make install
 			environment.</para>
 
 			<para>Make sure that the <literal>--sysconfdir=/etc</literal> matches the directory
-			where the the MIT kerberos library stores its <literal>krb5.conf</literal>. This is
+			where the the MIT Kerberos library stores its <literal>krb5.conf</literal>. This is
 			usually <literal>/etc</literal></para>
 		</section>
 
@@ -171,7 +171,7 @@ $ make install
 		<title>Testing and Code Coverage</title>
 
 		<para>Low level input parsers and such code should have unit tests
-		excercizing it. Use the <literal>make check</literal> command to run all
+		exercising it. Use the <literal>make check</literal> command to run all
 		the tests. If you run it from a subdirectory only the tests in that
 		directory will be run.</para>
 
diff --git a/doc/adcli.xml b/doc/adcli.xml
index 8ec48d4..93e1520 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>
@@ -70,7 +75,7 @@
 		<command>adcli add-member</command>
 		<arg choice="opt">--domain=domain.example.com</arg>
 		<arg choice="plain">group</arg>
-		<arg choice="plain" rep="repeat">user</arg>
+		<arg choice="plain" rep="repeat">user or computer</arg>
 	</cmdsynopsis>
 	<cmdsynopsis>
 		<command>adcli remove-member</command>
@@ -322,14 +327,14 @@ Password for Administrator:
 		</varlistentry>
 		<varlistentry>
 			<term><option>--service-name=<parameter>service</parameter></option></term>
-			<listitem><para>Additional service name for a kerberos
+			<listitem><para>Additional service name for a Kerberos
 			principal to be created on the computer account. This
 			option may be specified multiple times.</para></listitem>
 		</varlistentry>
 		<varlistentry>
 			<term><option>--user-principal=<parameter>host/name@REALM</parameter></option></term>
 			<listitem><para>Set the userPrincipalName field of the
-			computer account to this kerberos principal. If you omit
+			computer account to this Kerberos principal. If you omit
 			the value for this option, then a principal will be set
 			in the form of <code>host/host.example.com@REALM</code></para></listitem>
 		</varlistentry>
@@ -347,6 +352,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
@@ -355,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
@@ -390,6 +426,17 @@ Password for Administrator:
 			be used to specific an alternative location with the
 			help of an absolute path.</para></listitem>
 		</varlistentry>
+		<varlistentry>
+			<term><option>--ldap-passwd</option></term>
+			<listitem><para>Use LDAP add/mod operations to set the
+			machine account password instead of Kerberos. This
+			might help in some situations where Kerberos fails or
+			is unreliable. But please note that 'Change password'
+			or 'Reset password' permissions or similar might be
+			needed to make the LDAP operation work. Additionally
+			there will be no read-only domain controller (RODC)
+			support as there is with Kerberos.</para></listitem>
+		</varlistentry>
 	</variablelist>
 
 	<para>If supported on the AD side the
@@ -491,6 +538,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>--account-disable=<parameter>yes|no|true|false</parameter></option></term>
 			<listitem><para>Set or unset the ACCOUNTDISABLE
@@ -510,6 +571,34 @@ $ 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>--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
@@ -543,6 +632,17 @@ $ adcli update --login-ccache=/tmp/krbcc_123
 			be used to specific an alternative location with the
 			help of an absolute path.</para></listitem>
 		</varlistentry>
+		<varlistentry>
+			<term><option>--ldap-passwd</option></term>
+			<listitem><para>Use LDAP add/mod operations to set the
+			machine account password instead of Kerberos. This
+			might help in some situations where Kerberos fails or
+			is unreliable. But please note that 'Change password'
+			or 'Rest password' permissions or similar might be
+			needed to make the LDAP operation work. Additionally
+			there will be no read-only domain controller (RODC)
+			support as there is with Kerberos.</para></listitem>
+		</varlistentry>
 	</variablelist>
 
 	<para>If supported on the AD side the
@@ -646,7 +746,7 @@ $ adcli create-user Fry --domain=domain.example.com \
 			the new created user account, which should be the user's
 			NIS domain is the NIS/YP service of Active Directory's Services for Unix (SFU)
 			are used. This is needed to let the 'UNIX attributes' tab of older Active
-			Directoy versions show the set UNIX specific attributes. If not specified
+			Directory versions show the set UNIX specific attributes. If not specified
 			adcli will try to determine the NIS domain automatically if needed.
 			</para></listitem>
 		</varlistentry>
@@ -668,6 +768,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>
@@ -719,10 +834,12 @@ $ adcli delete-group Pilots --domain=domain.example.com
 
 	<para><command>adcli add-member</command> adds one or more users to a
 	group in the domain. The group is specified first, and then the various
-	users to be added.</para>
+	users or computers to be added.
+	You must use dollar sign for computer account (computername$)</para>
 
 <programlisting>
 $ adcli add-member --domain=domain.example.com Pilots Leela Scruffy
+$ adcli add-member --domain=domain.example.com servers srv-smb$
 </programlisting>
 
 	<para>The various global options can be used.</para>
@@ -801,14 +918,14 @@ Password for Administrator:
 		</varlistentry>
 		<varlistentry>
 			<term><option>--service-name=<parameter>service</parameter></option></term>
-			<listitem><para>Additional service name for a kerberos
+			<listitem><para>Additional service name for a Kerberos
 			principal to be created on the computer account. This
 			option may be specified multiple times.</para></listitem>
 		</varlistentry>
 		<varlistentry>
 			<term><option>--user-principal</option></term>
 			<listitem><para>Set the userPrincipalName field of the
-			computer account to this kerberos principal in the form
+			computer account to this Kerberos principal in the form
 			of <code>host/host.example.com@REALM</code></para></listitem>
 		</varlistentry>
 	</variablelist>
@@ -922,7 +1039,7 @@ Password for Administrator:
 
 	<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
+	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
diff --git a/library/Makefile.am b/library/Makefile.am
index 4829555..e046606 100644
--- a/library/Makefile.am
+++ b/library/Makefile.am
@@ -1,6 +1,6 @@
 include $(top_srcdir)/Makefile.decl
 
-INCLUDES = \
+AM_CPPFLAGS = \
 	-I$(top_srcdir) \
 	-DADCLI_UNSTABLE_API \
 	-DHOST_TRIPLET=\"$(host_triplet)\" \
diff --git a/library/adconn.c b/library/adconn.c
index 7bab852..37405cc 100644
--- a/library/adconn.c
+++ b/library/adconn.c
@@ -75,6 +75,7 @@ struct _adcli_conn_ctx {
 	char *domain_short;
 	char *domain_sid;
 	adcli_disco *domain_disco;
+	enum conn_is_writeable is_writeable;
 	char *default_naming_context;
 	char *configuration_naming_context;
 	char **supported_capabilities;
@@ -149,10 +150,14 @@ disco_dance_if_necessary (adcli_conn *conn)
 		return;
 
 	if (conn->domain_controller)
-		adcli_disco_host (conn->domain_controller, &conn->domain_disco);
+		adcli_disco_host (conn->domain_controller,
+		                  adcli_conn_get_use_ldaps (conn),
+		                  &conn->domain_disco);
 
 	else if (conn->domain_name)
-		adcli_disco_domain (conn->domain_name, &conn->domain_disco);
+		adcli_disco_domain (conn->domain_name,
+		                    adcli_conn_get_use_ldaps (conn),
+		                    &conn->domain_disco);
 
 	if (conn->domain_disco) {
 		if (!conn->domain_short && conn->domain_disco->domain_short) {
@@ -1181,6 +1186,26 @@ lookup_domain_sid (adcli_conn *conn)
 	}
 }
 
+static void
+lookup_is_writeable (adcli_conn *conn)
+{
+	char *attrs[] = { "NetLogon", NULL };
+	LDAPMessage *results;
+	int ret;
+
+	ret = ldap_search_ext_s (conn->ldap, "", LDAP_SCOPE_BASE,
+	                         "(&(NtVer=\\06\\00\\00\\00)(AAC=\\00\\00\\00\\00))",
+	                         attrs, 0, NULL, NULL, NULL, -1, &results);
+	if (ret == LDAP_SUCCESS) {
+		conn->is_writeable = disco_get_writeable (conn->ldap, results);
+		ldap_msgfree (results);
+	} else {
+		_adcli_ldap_handle_failure (conn->ldap, ADCLI_ERR_DIRECTORY,
+		                            "Couldn't lookup writeable state");
+		conn->is_writeable = IS_UNKNOWN;
+	}
+}
+
 static void
 conn_clear_state (adcli_conn *conn)
 {
@@ -1262,6 +1287,7 @@ adcli_conn_connect (adcli_conn *conn)
 
 	lookup_short_name (conn);
 	lookup_domain_sid (conn);
+	lookup_is_writeable (conn);
 	return ADCLI_SUCCESS;
 }
 
@@ -1702,11 +1728,9 @@ adcli_conn_server_has_sasl_mech (adcli_conn *conn,
 
 bool adcli_conn_is_writeable (adcli_conn *conn)
 {
-	disco_dance_if_necessary (conn);
-
-	if (conn->domain_disco == NULL) {
-		return false;
+	if (conn->is_writeable == IS_UNKNOWN) {
+		lookup_is_writeable (conn);
 	}
 
-	return ( (conn->domain_disco->flags & ADCLI_DISCO_WRITABLE) != 0);
+	return (conn->is_writeable == IS_WRITEABLE);
 }
diff --git a/library/adconn.h b/library/adconn.h
index 1d5faa8..3a3c32b 100644
--- a/library/adconn.h
+++ b/library/adconn.h
@@ -35,6 +35,13 @@ typedef enum {
 	ADCLI_LOGIN_USER_ACCOUNT = 1 << 2,
 } adcli_login_type;
 
+enum conn_is_writeable {
+	IS_UNKNOWN = 0,
+	IS_NOT_WRITEABLE = 1,
+	IS_WRITEABLE
+};
+
+
 #define ADCLI_CAP_OID                      "1.2.840.113556.1.4.800"
 #define ADCLI_CAP_LDAP_INTEG_OID           "1.2.840.113556.1.4.1791"
 #define ADCLI_CAP_V51_OID                  "1.2.840.113556.1.4.1670"
diff --git a/library/addisco.c b/library/addisco.c
index f3b3546..b2c5553 100644
--- a/library/addisco.c
+++ b/library/addisco.c
@@ -455,6 +455,39 @@ parse_disco (LDAP *ldap,
 	return usability;
 }
 
+enum conn_is_writeable disco_get_writeable (LDAP *ldap, LDAPMessage *message)
+{
+	adcli_disco *disco = NULL;
+	LDAPMessage *entry;
+	struct berval **bvs;
+	unsigned int flags;
+
+	entry = ldap_first_entry (ldap, message);
+	if (entry != NULL) {
+		bvs = ldap_get_values_len (ldap, entry, "NetLogon");
+		if (bvs != NULL) {
+			if (!bvs[0])
+				disco = NULL;
+			else
+				disco = parse_disco_data (bvs[0]);
+			ldap_value_free_len (bvs);
+		}
+	}
+
+	if (disco == NULL) {
+		return IS_UNKNOWN;
+	}
+
+	flags = disco->flags;
+	adcli_disco_free (disco);
+
+	if ( (flags & ADCLI_DISCO_WRITABLE) != 0) {
+		return IS_WRITEABLE;
+	} else {
+		return  IS_NOT_WRITEABLE;
+	}
+}
+
 static int
 ldap_disco_poller (LDAP **ldap,
                    LDAPMessage **message,
@@ -500,9 +533,144 @@ ldap_disco_poller (LDAP **ldap,
 	return found;
 }
 
+static int
+ldaps_disco (const char *domain,
+            srvinfo *srv,
+            adcli_disco **results)
+{
+	char *attrs[] = { "NetLogon", NULL };
+	LDAP *ldap[DISCO_COUNT];
+	const char *addrs[DISCO_COUNT];
+	int found = ADCLI_DISCO_UNUSABLE;
+	LDAPMessage *message;
+	char buffer[1024];
+	struct addrinfo hints;
+	struct addrinfo *res;
+	const char *scheme;
+	int msgidp;
+	int version;
+	char *url;
+	char *filter;
+	char *value;
+	int num;
+	int ret;
+	struct timeval interval;
+	int parsed;
+
+	if (domain) {
+		value = _adcli_ldap_escape_filter (domain);
+		return_val_if_fail (value != NULL, 0);
+		if (asprintf (&filter, "(&(DnsDomain=%s)(NtVer=\\06\\00\\00\\00))", value) < 0)
+			return_val_if_reached (0);
+		free (value);
+	} else {
+		if (asprintf (&filter, "(&(NtVer=\\06\\00\\00\\00)(AAC=\\00\\00\\00\\00))") < 0)
+			return_val_if_reached (0);
+	}
+
+	memset (addrs, 0, sizeof (addrs));
+	memset (ldap, 0, sizeof (ldap));
+
+	scheme = "ldaps";
+
+	/*
+	 * The ai_socktype and ai_protocol hint fields are unused below,
+	 * but are set in order to prevent duplicate returns from
+	 * getaddrinfo().
+	 */
+	memset (&hints, 0, sizeof (hints));
+	hints.ai_socktype = SOCK_STREAM;
+	hints.ai_protocol = IPPROTO_TCP;
+	/* For ldaps we have to use the DNS name of the LDAP server so that
+	 * libldap can check if the name from the certificate matches the
+	 * server name. With AI_NUMERICHOST we check if srv->hostname is an IP
+	 * address or not. */
+	hints.ai_flags |= AI_NUMERICHOST;
+	hints.ai_flags |= AI_NUMERICSERV;
+#ifdef AI_ADDRCONFIG
+	hints.ai_flags |= AI_ADDRCONFIG;
+#endif
+
+	for (num = 0; ADCLI_DISCO_UNUSABLE == found && srv != NULL && num < DISCO_COUNT; srv = srv->next) {
+		ret = getaddrinfo (srv->hostname, "389", &hints, &res);
+		if (ret == 0) {
+			ret = getnameinfo (res->ai_addr, res->ai_addrlen,
+			                   buffer, sizeof (buffer), NULL, 0,
+			                   NI_NAMEREQD);
+			freeaddrinfo (res);
+			if (ret != 0) {
+				_adcli_warn ("Couldn't resolve server name: %s: %s",
+				             srv->hostname, gai_strerror (ret));
+				continue;
+			}
+		} else if (ret != 0 && ret != EAI_NONAME) {
+			_adcli_warn ("Couldn't resolve server host: %s: %s",
+			             srv->hostname, gai_strerror (ret));
+			continue;
+		}
+
+		if (asprintf (&url, "%s://%s", scheme, buffer) < 0) {
+			return_val_if_reached (0);
+		}
+
+		ret = ldap_initialize (&ldap[num], url);
+		if (ret == LDAP_SUCCESS) {
+			version = LDAP_VERSION3;
+			ldap_set_option (ldap[num], LDAP_OPT_PROTOCOL_VERSION, &version);
+			ldap_set_option (ldap[num], LDAP_OPT_REFERRALS , 0);
+			addrs[num] = srv->hostname;
+
+		} else {
+			_adcli_err ("Couldn't perform discovery on server: %s: %s", url, ldap_err2string (ret));
+			free (url);
+			continue;
+		}
+		free (url);
+
+		_adcli_info ("Sending LDAPS NetLogon ping to domain controller: %s", addrs[num]);
+
+		ret = ldap_search_ext (ldap[num], "", LDAP_SCOPE_BASE,
+		                       filter, attrs, 0, NULL, NULL, NULL,
+		                       -1, &msgidp);
+
+		if (ret != LDAP_SUCCESS) {
+			_adcli_ldap_handle_failure (ldap[num], ADCLI_ERR_CONFIG,
+			                            "Couldn't perform discovery search");
+			ldap_unbind_ext_s (ldap[num], NULL, NULL);
+			ldap[num] = NULL;
+			continue;
+		}
+
+		/* From https://msdn.microsoft.com/en-us/library/ff718294.aspx first
+		 * five DCs are given 0.4 seconds timeout, next five are given 0.2
+		 * seconds, and the rest are given 0.1 seconds
+		 */
+		if (num < 5) {
+			interval.tv_usec = 400000;
+		} else if (num < 10) {
+			interval.tv_usec = 200000;
+		} else {
+			interval.tv_usec = 100000;
+		}
+		select (0, NULL, NULL, NULL, &interval);
+
+		parsed = ldap_disco_poller (&(ldap[num]), &message, results, &(addrs[num]));
+		if (ldap[num] != NULL) {
+			ldap_unbind_ext_s (ldap[num], NULL, NULL);
+		}
+		if (parsed > found) {
+			found = parsed;
+		}
+	}
+
+	free (filter);
+	return found;
+}
+
 static int
 ldap_disco (const char *domain,
             srvinfo *srv,
+            bool use_ldaps,
             adcli_disco **results)
 {
 	char *attrs[] = { "NetLogon", NULL };
@@ -525,6 +693,7 @@ ldap_disco (const char *domain,
 	int ret;
 	int have_any = 0;
 	struct timeval interval;
+	srvinfo *my_srv;
 
 	if (domain) {
 		value = _adcli_ldap_escape_filter (domain);
@@ -559,11 +728,12 @@ ldap_disco (const char *domain,
 	hints.ai_flags |= AI_ADDRCONFIG;
 #endif
 
-	for (num = 0; srv != NULL; srv = srv->next) {
-		ret = getaddrinfo (srv->hostname, "389", &hints, &res);
+	my_srv = srv;
+	for (num = 0; my_srv != NULL; my_srv = my_srv->next) {
+		ret = getaddrinfo (my_srv->hostname, "389", &hints, &res);
 		if (ret != 0) {
 			_adcli_warn ("Couldn't resolve server host: %s: %s",
-			             srv->hostname, gai_strerror (ret));
+			             my_srv->hostname, gai_strerror (ret));
 			continue;
 		}
 
@@ -588,7 +758,7 @@ ldap_disco (const char *domain,
 				version = LDAP_VERSION3;
 				ldap_set_option (ldap[num], LDAP_OPT_PROTOCOL_VERSION, &version);
 				ldap_set_option (ldap[num], LDAP_OPT_REFERRALS , 0);
-				addrs[num] = srv->hostname;
+				addrs[num] = my_srv->hostname;
 				have_any = 1;
 				num++;
 
@@ -671,6 +841,11 @@ ldap_disco (const char *domain,
 	}
 
 	free (filter);
+
+	if (found == ADCLI_DISCO_UNUSABLE && use_ldaps) {
+		found = ldaps_disco (domain, srv, results);
+	}
+
 	return found;
 }
 
@@ -698,7 +873,7 @@ fill_disco (adcli_disco **results,
 }
 
 static int
-site_disco (adcli_disco *disco,
+site_disco (adcli_disco *disco, bool use_ldaps,
             adcli_disco **results)
 {
 	srvinfo *srv;
@@ -741,7 +916,7 @@ site_disco (adcli_disco *disco,
 	 * Now that we have discovered the site domain controllers do a
 	 * second round of cldap discovery.
 	 */
-	found = ldap_disco (disco->domain, srv, results);
+	found = ldap_disco (disco->domain, srv, use_ldaps, results);
 
 	fill_disco (results, ADCLI_DISCO_MAYBE,
 	            disco->domain, disco->client_site, srv);
@@ -752,7 +927,7 @@ site_disco (adcli_disco *disco,
 }
 
 int
-adcli_disco_domain (const char *domain,
+adcli_disco_domain (const char *domain, bool use_ldaps,
                     adcli_disco **results)
 {
 	char *rrname;
@@ -791,10 +966,10 @@ adcli_disco_domain (const char *domain,
 	if (ret != 0)
 		return 0;
 
-	found = ldap_disco (domain, srv, results);
+	found = ldap_disco (domain, srv, use_ldaps, results);
 	if (found == ADCLI_DISCO_MAYBE) {
 		assert (*results);
-		found = site_disco (*results, results);
+		found = site_disco (*results, use_ldaps, results);
 	}
 
 	fill_disco (results, ADCLI_DISCO_MAYBE, domain, NULL, srv);
@@ -804,7 +979,7 @@ adcli_disco_domain (const char *domain,
 }
 
 int
-adcli_disco_host (const char *host,
+adcli_disco_host (const char *host, bool use_ldaps,
                   adcli_disco **results)
 {
 	srvinfo srv;
@@ -817,7 +992,7 @@ adcli_disco_host (const char *host,
 	memset (&srv, 0, sizeof (srv));
 	srv.hostname = (char *)host;
 
-	return ldap_disco (NULL, &srv, results);
+	return ldap_disco (NULL, &srv, use_ldaps, results);
 }
 
 void
diff --git a/library/addisco.h b/library/addisco.h
index 06b69a9..718db7d 100644
--- a/library/addisco.h
+++ b/library/addisco.h
@@ -56,10 +56,10 @@ typedef struct _adcli_disco {
 	struct _adcli_disco *next;
 } adcli_disco;
 
-int           adcli_disco_domain            (const char *domain,
+int           adcli_disco_domain            (const char *domain, bool use_ldaps,
                                              adcli_disco **disco);
 
-int           adcli_disco_host              (const char *host,
+int           adcli_disco_host              (const char *host, bool use_ldaps,
                                              adcli_disco **disco);
 
 void          adcli_disco_free              (adcli_disco *disco);
@@ -72,4 +72,6 @@ enum {
 
 int           adcli_disco_usable            (adcli_disco *disco);
 
+enum conn_is_writeable disco_get_writeable (LDAP *ldap, LDAPMessage *message);
+
 #endif /* ADDISCO_H_ */
diff --git a/library/adenroll.c b/library/adenroll.c
index 2b830a4..5ae1215 100644
--- a/library/adenroll.c
+++ b/library/adenroll.c
@@ -43,6 +43,8 @@
 #include <unistd.h>
 #include <sys/stat.h>
 #include <fcntl.h>
+#include <iconv.h>
+#include <lber.h>
 
 #ifndef SAMBA_DATA_TOOL
 #define SAMBA_DATA_TOOL "/usr/bin/net"
@@ -93,14 +95,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_ACCOUNTDISABLE             0x0002
-#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;
@@ -153,9 +147,13 @@ 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;
 	bool account_disable;
 	int account_disable_explicit;
 	char *description;
+	char **setattr;
+	char **delattr;
 };
 
 static const char *
@@ -801,9 +799,157 @@ 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 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 struct berval *get_unicode_pwd (char *pwd)
+{
+	iconv_t cd;
+	size_t s;
+	char *in = NULL;
+	char *in_ptr;
+	size_t in_size;
+	size_t len;
+	char *out = NULL;
+	char *out_ptr;
+	size_t out_size;
+	struct berval *bv = NULL;
+
+	if (pwd == NULL) {
+		return NULL;
+	}
+
+	if (asprintf (&in, "\"%s\"",pwd) < 0) {
+		return NULL;
+	}
+	in_ptr = in;
+	len = in_size = strlen (in);
+
+	out_size = 2*in_size;
+	out = malloc (out_size * sizeof (char));
+	out_ptr = out;
+
+	cd = iconv_open ("UTF-16LE", "UTF-8");
+	if (cd == (iconv_t) -1 ) {
+		goto done;
+	}
+
+	s = iconv (cd, &in_ptr,  &in_size, &out_ptr, &out_size);
+	if (s == (size_t) -1 || out_size != 0) {
+		iconv_close (cd);
+		goto done;
+	}
+
+	s = iconv (cd, NULL, NULL, &out_ptr, &out_size);
+	if (s == (size_t) -1) {
+		iconv_close (cd);
+		goto done;
+	}
+
+	if (iconv_close (cd) != 0) {
+		goto done;
+	}
+
+	bv = malloc (sizeof(struct berval));
+	if (bv == NULL) {
+		goto done;
+	}
+
+	bv->bv_len = 2*len;
+	bv->bv_val = out;
+
+done:
+	free (in);
+	if (bv == NULL) {
+		free (out);
+	}
+
+	return bv;
+}
+
 static adcli_result
 create_computer_account (adcli_enroll *enroll,
-                         LDAP *ldap)
+                         LDAP *ldap, int ldap_passwd)
 {
 	char *vals_objectClass[] = { enroll->is_service ? "msDS-ManagedServiceAccount" : "computer", NULL };
 	LDAPMod objectClass = { LDAP_MOD_ADD, "objectClass", { vals_objectClass, } };
@@ -826,12 +972,17 @@ create_computer_account (adcli_enroll *enroll,
 	LDAPMod servicePrincipalName = { LDAP_MOD_ADD, "servicePrincipalName", { enroll->service_principals, } };
 	char *vals_description[] = { enroll->description, NULL };
 	LDAPMod description = { LDAP_MOD_ADD, "description", { vals_description, }, };
+	struct berval *vals_unicodePwd[] = { NULL, NULL };
+	LDAPMod unicodePwd = { LDAP_MOD_ADD | LDAP_MOD_BVALUES, "unicodePwd", { NULL, } };
 
 	char *val = NULL;
 
 	int ret;
 	size_t c;
 	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,22 +996,57 @@ create_computer_account (adcli_enroll *enroll,
 		&userPrincipalName,
 		&servicePrincipalName,
 		&description,
+		&unicodePwd,
 		NULL
 	};
 
 	size_t mods_count = sizeof (all_mods) / sizeof (LDAPMod *);
-	LDAPMod *mods[mods_count];
+	LDAPMod **mods;
+
+	if (ldap_passwd) {
+		_adcli_info ("Trying to set %s password with LDAP", s_or_c (enroll));
+
+		vals_unicodePwd[0] = get_unicode_pwd (enroll->computer_password);
+		if (vals_unicodePwd[0] == NULL) {
+			return ADCLI_ERR_FAIL;
+		}
+		unicodePwd.mod_vals.modv_bvals = vals_unicodePwd;
+	}
 
 	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 (enroll->dont_expire_password_explicit
+		       && !adcli_enroll_get_dont_expire_password (enroll)) {
+		uac &= ~(UAC_DONT_EXPIRE_PASSWORD);
+	}
+
+	if (asprintf (&uac_str, "%d", uac) < 0) {
+		ber_bvfree (vals_unicodePwd[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);
+		ber_bvfree (vals_unicodePwd[0]);
 		return ret;
 	}
 	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 */
@@ -868,9 +1054,17 @@ 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);
+	ber_bvfree (vals_unicodePwd[0]);
+	ldap_mods_free (extra_mods, 1);
+	free (mods);
+	free (uac_str);
 	free (val);
 
 	/*
@@ -1179,7 +1373,7 @@ get_service_account_name_from_ldap (adcli_enroll *enroll, LDAPMessage *results)
 
 static adcli_result
 locate_or_create_computer_account (adcli_enroll *enroll,
-                                   int allow_overwrite)
+                                   int allow_overwrite, int ldap_passwd)
 {
 	LDAPMessage *results = NULL;
 	LDAPMessage *entry = NULL;
@@ -1241,7 +1435,7 @@ locate_or_create_computer_account (adcli_enroll *enroll,
 
 	res = validate_computer_account (enroll, allow_overwrite, entry != NULL);
 	if (res == ADCLI_SUCCESS && entry == NULL)
-		res = create_computer_account (enroll, ldap);
+		res = create_computer_account (enroll, ldap, ldap_passwd);
 
 	/* Service account already exists, just continue and update the
 	 * password */
@@ -1255,6 +1449,47 @@ locate_or_create_computer_account (adcli_enroll *enroll,
 	return res;
 }
 
+static adcli_result
+set_password_with_ldap (adcli_enroll *enroll)
+{
+	LDAP *ldap;
+	int ret;
+	struct berval *vals_unicodePwd[] = { NULL, NULL };
+	LDAPMod unicodePwd = { LDAP_MOD_REPLACE | LDAP_MOD_BVALUES, "unicodePwd", { NULL, } };
+
+	LDAPMod *all_mods[] = {
+		&unicodePwd,
+		NULL
+	};
+
+	ldap = adcli_conn_get_ldap_connection (enroll->conn);
+	return_unexpected_if_fail (ldap != NULL);
+
+	vals_unicodePwd[0] = get_unicode_pwd (enroll->computer_password);
+	return_unexpected_if_fail (vals_unicodePwd[0] != NULL);
+	unicodePwd.mod_vals.modv_bvals = vals_unicodePwd;
+
+	_adcli_info ("Trying to set %s password with LDAP", s_or_c (enroll));
+
+	ret = ldap_modify_ext_s (ldap, enroll->computer_dn, all_mods, NULL, NULL);
+	ber_bvfree (vals_unicodePwd[0]);
+
+	if (ret == LDAP_INSUFFICIENT_ACCESS || ret == LDAP_OBJECT_CLASS_VIOLATION ||
+	    ret == LDAP_UNWILLING_TO_PERFORM || ret == LDAP_CONSTRAINT_VIOLATION) {
+		return _adcli_ldap_handle_failure (ldap, ADCLI_ERR_CREDENTIALS,
+		                                   "Insufficient permissions to set password for: %s",
+		                                   enroll->computer_dn);
+
+	} else if (ret != LDAP_SUCCESS) {
+		return _adcli_ldap_handle_failure (ldap, ADCLI_ERR_DIRECTORY,
+		                                   "Couldn't set password for: %s",
+		                                   enroll->computer_dn);
+	}
+
+	_adcli_info ("Set password for: %s", enroll->computer_dn);
+	return ADCLI_SUCCESS;
+}
+
 static adcli_result
 set_password_with_user_creds (adcli_enroll *enroll)
 {
@@ -1279,6 +1514,8 @@ set_password_with_user_creds (adcli_enroll *enroll)
 	memset (&result_string, 0, sizeof (result_string));
 	memset (&result_code_string, 0, sizeof (result_code_string));
 
+	_adcli_info ("Trying to set %s password with Kerberos", s_or_c (enroll));
+
 	code = krb5_set_password_using_ccache (k5, ccache, enroll->computer_password,
 	                                       enroll->computer_principal, &result_code,
 	                                       &result_code_string, &result_string);
@@ -1341,6 +1578,8 @@ set_password_with_computer_creds (adcli_enroll *enroll)
 	k5 = adcli_conn_get_krb5_context (enroll->conn);
 	return_unexpected_if_fail (k5 != NULL);
 
+	_adcli_info ("Trying to change %s password with Kerberos", s_or_c (enroll));
+
 	code = _adcli_kinit_computer_creds (enroll->conn, "kadmin/changepw", NULL, &creds);
 	if (code != 0) {
 		_adcli_err ("Couldn't get change password ticket for %s account: %s: %s",
@@ -1395,8 +1634,12 @@ set_password_with_computer_creds (adcli_enroll *enroll)
 }
 
 static adcli_result
-set_computer_password (adcli_enroll *enroll)
+set_computer_password (adcli_enroll *enroll, int ldap_passwd)
 {
+	if (ldap_passwd) {
+		return set_password_with_ldap (enroll);
+	}
+
 	if (adcli_conn_get_login_type (enroll->conn) == ADCLI_LOGIN_COMPUTER_ACCOUNT)
 		return set_password_with_computer_creds (enroll);
 	else
@@ -1577,6 +1820,14 @@ static char *get_user_account_control (adcli_enroll *enroll)
 		}
 	}
 
+	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 (enroll->account_disable_explicit) {
 		if (adcli_enroll_get_account_disable (enroll)) {
 			uac |= UAC_ACCOUNTDISABLE;
@@ -1627,6 +1878,7 @@ update_computer_account (adcli_enroll *enroll)
 	free (value);
 
 	if (res == ADCLI_SUCCESS && (enroll->trusted_for_delegation_explicit ||
+	                             enroll->dont_expire_password_explicit ||
 	                             enroll->account_disable_explicit)) {
 		char *vals_userAccountControl[] = { NULL , NULL };
 		LDAPMod userAccountControl = { LDAP_MOD_REPLACE, "userAccountControl", { vals_userAccountControl, } };
@@ -1681,6 +1933,22 @@ 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 == 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);
 }
@@ -2109,13 +2377,6 @@ update_samba_data (adcli_enroll *enroll)
 	}
 	argv_sid[0] = argv_pw[0];
 
-	_adcli_info ("Trying to set Samba secret.");
-	ret = _adcli_call_external_program (argv_pw[0], argv_pw,
-	                                    enroll->computer_password, NULL, NULL);
-	if (ret != ADCLI_SUCCESS) {
-		_adcli_err ("Failed to set Samba computer account password.");
-	}
-
 	argv_sid[2] = (char *) adcli_conn_get_domain_sid (enroll->conn);
 	if (argv_sid[2] == NULL) {
 		_adcli_err ("Domain SID not available.");
@@ -2129,6 +2390,13 @@ update_samba_data (adcli_enroll *enroll)
 		}
 	}
 
+	_adcli_info ("Trying to set Samba secret.");
+	ret = _adcli_call_external_program (argv_pw[0], argv_pw,
+	                                    enroll->computer_password, NULL, NULL);
+	if (ret != ADCLI_SUCCESS) {
+		_adcli_err ("Failed to set Samba computer account password.");
+	}
+
 	return ret;
 }
 
@@ -2298,7 +2566,7 @@ enroll_join_or_update_tasks (adcli_enroll *enroll,
 			adcli_enroll_set_kvno (enroll, 0);
 		}
 
-		res = set_computer_password (enroll);
+		res = set_computer_password (enroll, flags & ADCLI_ENROLL_LDAP_PASSWD);
 		if (res != ADCLI_SUCCESS)
 			return res;
 	}
@@ -2451,7 +2719,8 @@ adcli_enroll_join (adcli_enroll *enroll,
 		return res;
 
 	/* This is where it really happens */
-	res = locate_or_create_computer_account (enroll, flags & ADCLI_ENROLL_ALLOW_OVERWRITE);
+	res = locate_or_create_computer_account (enroll, flags & ADCLI_ENROLL_ALLOW_OVERWRITE,
+	                                         flags & ADCLI_ENROLL_LDAP_PASSWD);
 	if (res != ADCLI_SUCCESS)
 		return res;
 
@@ -2633,8 +2902,7 @@ adcli_enroll_delete (adcli_enroll *enroll,
 }
 
 adcli_result
-adcli_enroll_password (adcli_enroll *enroll,
-                       adcli_enroll_flags password_flags)
+adcli_enroll_password (adcli_enroll *enroll)
 {
 	adcli_result res = ADCLI_SUCCESS;
 	LDAP *ldap;
@@ -2673,7 +2941,7 @@ adcli_enroll_password (adcli_enroll *enroll,
 		}
 	}
 
-	return set_computer_password (enroll);
+	return set_computer_password (enroll, 0);
 }
 
 adcli_enroll *
@@ -2734,6 +3002,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);
@@ -3060,6 +3329,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));
@@ -3206,6 +3477,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;
+}
+
 bool
 adcli_enroll_get_account_disable (adcli_enroll *enroll)
 {
@@ -3295,6 +3584,96 @@ 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;
+}
+
+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
 
 #include "test.h"
@@ -3364,12 +3743,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 8b1c1c7..da2adc5 100644
--- a/library/adenroll.h
+++ b/library/adenroll.h
@@ -31,6 +31,7 @@ typedef enum {
 	ADCLI_ENROLL_ALLOW_OVERWRITE = 1 << 2,
 	ADCLI_ENROLL_PASSWORD_VALID = 1 << 3,
 	ADCLI_ENROLL_ADD_SAMBA_DATA = 1 << 4,
+	ADCLI_ENROLL_LDAP_PASSWD = 1 << 5,
 } adcli_enroll_flags;
 
 typedef struct _adcli_enroll adcli_enroll;
@@ -54,8 +55,7 @@ adcli_result       adcli_enroll_show_computer_attribute (adcli_enroll *enroll);
 adcli_result       adcli_enroll_delete                  (adcli_enroll *enroll,
                                                          adcli_enroll_flags delete_flags);
 
-adcli_result       adcli_enroll_password                (adcli_enroll *enroll,
-                                                         adcli_enroll_flags password_flags);
+adcli_result       adcli_enroll_password                (adcli_enroll *enroll);
 
 adcli_enroll *     adcli_enroll_new                     (adcli_conn *conn);
 
@@ -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);
+
 bool               adcli_enroll_get_account_disable     (adcli_enroll *enroll);
 void               adcli_enroll_set_account_disable     (adcli_enroll *enroll,
                                                          bool value);
@@ -134,6 +138,14 @@ 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);
+
+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/library/adentry.c b/library/adentry.c
index 1cc0518..0d9b9af 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;
 }
 
@@ -405,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/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);
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)
diff --git a/library/adutil.c b/library/adutil.c
index 9b0c47f..4bb06a3 100644
--- a/library/adutil.c
+++ b/library/adutil.c
@@ -29,6 +29,7 @@
 
 #include <assert.h>
 #include <ctype.h>
+#include <endian.h>
 #include <errno.h>
 #include <stdio.h>
 #include <stdlib.h>
diff --git a/tools/Makefile.am b/tools/Makefile.am
index 1cdf451..71ec14d 100644
--- a/tools/Makefile.am
+++ b/tools/Makefile.am
@@ -1,6 +1,6 @@
 include $(top_srcdir)/Makefile.decl
 
-INCLUDES = \
+AM_CPPFLAGS = \
 	-I$(top_srcdir) \
 	-I$(top_srcdir)/library \
 	-DKRB5_CONFIG=\""$(sysconfdir)/krb5.conf"\" \
diff --git a/tools/computer.c b/tools/computer.c
index 6e309d9..58ade1f 100644
--- a/tools/computer.c
+++ b/tools/computer.c
@@ -110,11 +110,15 @@ 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,
+	opt_setattr,
+	opt_delattr,
 	opt_use_ldaps,
 	opt_account_disable,
+	opt_ldap_passwd,
 } Option;
 
 static adcli_tool_desc common_usages[] = {
@@ -144,11 +148,15 @@ 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_account_disable, "set/unset the ACCOUNTDISABLE 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" },
+	{ 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"
@@ -162,6 +170,7 @@ static adcli_tool_desc common_usages[] = {
 	{ opt_add_samba_data, "add domain SID and computer account password\n"
 	                      "to the Samba specific configuration database" },
 	{ opt_samba_data_tool, "Absolute path to the tool used for add-samba-data" },
+	{ opt_ldap_passwd, "Use LDAP add/mod operation to set/change password" },
 	{ opt_verbose, "show verbose progress and failure messages", },
 	{ 0 },
 };
@@ -307,6 +316,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_account_disable:
 		if (strcasecmp (optarg, "true") == 0 || strcasecmp (optarg, "yes") == 0) {
 			adcli_enroll_set_account_disable (enroll, true);
@@ -323,6 +339,18 @@ 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_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;
@@ -334,6 +362,7 @@ parse_option (Option opt,
 	case opt_show_password:
 	case opt_one_time_password:
 	case opt_add_samba_data:
+	case opt_ldap_passwd:
 		assert (0 && "not reached");
 		break;
 	}
@@ -391,13 +420,16 @@ 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 },
 		{ "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 },
 		{ "add-samba-data", no_argument, NULL, opt_add_samba_data },
 		{ "samba-data-tool", required_argument, 0, opt_samba_data_tool },
+		{ "ldap-passwd", no_argument, NULL, opt_ldap_passwd },
 		{ "verbose", no_argument, NULL, opt_verbose },
 		{ "help", no_argument, NULL, 'h' },
 		{ 0 },
@@ -429,6 +461,9 @@ adcli_tool_computer_join (adcli_conn *conn,
 		case opt_add_samba_data:
 			flags |= ADCLI_ENROLL_ADD_SAMBA_DATA;
 			break;
+		case opt_ldap_passwd:
+			flags |= ADCLI_ENROLL_LDAP_PASSWD;
+			break;
 		case 'h':
 		case '?':
 		case ':':
@@ -513,9 +548,12 @@ 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 },
+		{ "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 },
+		{ "dont-expire-password", required_argument, NULL, opt_dont_expire_password },
 		{ "account-disable", required_argument, NULL, opt_account_disable },
 		{ "add-service-principal", required_argument, NULL, opt_add_service_principal },
 		{ "remove-service-principal", required_argument, NULL, opt_remove_service_principal },
@@ -523,6 +561,7 @@ adcli_tool_computer_update (adcli_conn *conn,
 		{ "show-password", no_argument, NULL, opt_show_password },
 		{ "add-samba-data", no_argument, NULL, opt_add_samba_data },
 		{ "samba-data-tool", required_argument, 0, opt_samba_data_tool },
+		{ "ldap-passwd", no_argument, NULL, opt_ldap_passwd },
 		{ "verbose", no_argument, NULL, opt_verbose },
 		{ "help", no_argument, NULL, 'h' },
 		{ 0 },
@@ -550,6 +589,9 @@ adcli_tool_computer_update (adcli_conn *conn,
 		case opt_add_samba_data:
 			flags |= ADCLI_ENROLL_ADD_SAMBA_DATA;
 			break;
+		case opt_ldap_passwd:
+			flags |= ADCLI_ENROLL_LDAP_PASSWD;
+			break;
 		case 'h':
 		case '?':
 		case ':':
@@ -885,7 +927,7 @@ adcli_tool_computer_reset (adcli_conn *conn,
 	parse_fqdn_or_name (enroll, argv[0]);
 	adcli_enroll_reset_computer_password (enroll);
 
-	res = adcli_enroll_password (enroll, 0);
+	res = adcli_enroll_password (enroll);
 	if (res != ADCLI_SUCCESS) {
 		warnx ("resetting %s in %s domain failed: %s", argv[0],
 		       adcli_conn_get_domain_name (conn),
diff --git a/tools/entry.c b/tools/entry.c
index 05e4313..433f8c0 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,
@@ -644,6 +743,7 @@ adcli_tool_member_add (adcli_conn *conn,
 
 	static adcli_tool_desc usages[] = {
 		{ 0, "usage: adcli add-member --domain=xxxx group user ..." },
+		{ 0, "       adcli add-member --domain=xxxx group computer$ ... (dollar sign is required for computer account)" },
 		{ 0 },
 	};
 
diff --git a/tools/info.c b/tools/info.c
index c63e0ff..fe1246a 100644
--- a/tools/info.c
+++ b/tools/info.c
@@ -168,14 +168,14 @@ adcli_tool_info (adcli_conn *unused,
 	}
 
 	if (server) {
-		adcli_disco_host (server, &disco);
+		adcli_disco_host (server, false, &disco);
 		if (disco == NULL) {
 			warnx ("couldn't discover domain controller: %s", server);
 			return 1;
 		}
 		for_host = 1;
 	} else if (domain) {
-		adcli_disco_domain (domain, &disco);
+		adcli_disco_domain (domain, false, &disco);
 		if (disco == NULL) {
 			warnx ("couldn't discover domain: %s", domain);
 			return 1;
diff --git a/tools/tools.c b/tools/tools.c
index d0dcf98..7e382ae 100644
--- a/tools/tools.c
+++ b/tools/tools.c
@@ -36,6 +36,7 @@
 #include <paths.h>
 #include <stdio.h>
 #include <unistd.h>
+#include <termios.h>
 
 
 static char *adcli_temp_directory = NULL;
@@ -63,6 +64,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", },
@@ -73,6 +75,11 @@ struct {
 static char
 short_option (int opt)
 {
+	// isalpha and isdigit require the argument to be representable as unsigned char
+	// if they're not representable as unsigned char, then we assume they're not printable -
+	// matching for example the enumerator definitions in computer.c
+	if (opt != (int)(unsigned char)opt)
+		return 0;
 	if (isalpha (opt) || isdigit (opt))
 		return (char)opt;
 	return 0;
@@ -202,6 +209,47 @@ command_usage (void)
 	printf ("\nSee 'adcli <command> --help' for more information\n");
 }
 
+static char *get_password (const char *prompt)
+{
+	int ret;
+	struct termios termios;
+	struct termios orig_termios;
+	char *buf = NULL;
+	size_t buf_len = 0;
+
+	ret = tcgetattr (fileno (stdin), &termios);
+	if (ret != 0 ) {
+		return NULL;
+	}
+
+	orig_termios = termios;
+	termios.c_lflag &= ~ECHO;
+
+	ret = tcsetattr (fileno (stdin), TCSAFLUSH, &termios);
+	if (ret != 0) {
+		return NULL;
+	}
+
+	fprintf (stdout, "%s", prompt);
+	fflush (stdout);
+
+	ret = getline (&buf, &buf_len, stdin);
+	tcsetattr (fileno (stdin), TCSAFLUSH, &orig_termios);
+	if (ret <= 0) {
+		free (buf);
+		return NULL;
+	}
+
+	if (buf[ret - 1] == '\n') {
+		/* remove new-line character from the end of the buffer and
+		 * echo it to stdout */
+		buf[ret - 1] = '\0';
+		fprintf (stdout, "\n");
+	}
+
+	return buf;
+}
+
 char *
 adcli_prompt_password_func (adcli_login_type login_type,
                             const char *name,
@@ -215,7 +263,7 @@ adcli_prompt_password_func (adcli_login_type login_type,
 	if (asprintf (&prompt, "Password for %s: ", name) < 0)
 		return_val_if_reached (NULL);
 
-	password = getpass (prompt);
+	password = get_password (prompt);
 	free (prompt);
 
 	if (password == NULL)
@@ -223,6 +271,7 @@ adcli_prompt_password_func (adcli_login_type login_type,
 
 	result = strdup (password);
 	adcli_mem_clear (password, strlen (password));
+	free (password);
 
 	return result;
 }
@@ -337,7 +386,14 @@ setup_krb5_conf_directory (adcli_conn *conn)
 	}
 
 	if (!failed) {
-		if (mkdtemp (directory) == NULL) {
+		mode_t old_umask;
+		char *dtemp = NULL;
+
+		old_umask = umask (0077);
+		dtemp = mkdtemp (directory);
+		umask (old_umask);
+
+		if (dtemp == NULL) {
 			errn = errno;
 			failed = 1;
 			warnx ("couldn't create temporary directory in: %s: %s",
@@ -538,6 +594,12 @@ main (int argc,
 
 		if (conn)
 			adcli_conn_unref (conn);
+#ifdef VENDOR_MSG
+		if (ret != 0) {
+			fprintf (stderr, VENDOR_MSG"\n");
+		}
+#endif
+
 		return ret;
 	}
 
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[]);

Debdiff

[The following lists of changes regard files as different if they have different names, permissions or owners.]

Files in second set of .debs but not in first

-rw-r--r--  root/root   /usr/lib/debug/.build-id/56/da08414d212b73733c4c1f00f71588c368f6bc.debug

Files in first set of .debs but not in second

-rw-r--r--  root/root   /usr/lib/debug/.build-id/f8/8e560d3da5f5e847053293e5dec551d12b7f45.debug

Control files of package adcli: lines which differ (wdiff format)

  • Depends: libsasl2-modules-gssapi-mit, libc6 (>= 2.34), libgssapi-krb5-2 (>= 1.6.dfsg.2), libk5crypto3 (>= 1.7+dfsg), 1.20), libkrb5-3 (>= 1.12~alpha1+dfsg), libldap-2.5-0 (>= 2.5.4)

Control files of package adcli-dbgsym: lines which differ (wdiff format)

  • Build-Ids: f88e560d3da5f5e847053293e5dec551d12b7f45 56da08414d212b73733c4c1f00f71588c368f6bc

More details

Full run details