Blame SOURCES/0003-entry-add-passwd-user-sub-command.patch

2d8b37
From 6a673b236dfdfdf9c73cc3d2ccf3949eb1a5ddd0 Mon Sep 17 00:00:00 2001
2d8b37
From: Sumit Bose <sbose@redhat.com>
2d8b37
Date: Fri, 11 Jun 2021 12:47:37 +0200
2d8b37
Subject: [PATCH 3/3] entry: add passwd-user sub-command
2d8b37
2d8b37
The new command allows to set or reset a user password with the help of
2d8b37
an account privileged to set the password.
2d8b37
2d8b37
Resolves: https://bugzilla.redhat.com/show_bug.cgi?id=1952828
2d8b37
---
2d8b37
 doc/adcli.xml     |  20 +++++++
2d8b37
 library/adentry.c | 138 ++++++++++++++++++++++++++++++++++++++++++++++
2d8b37
 library/adentry.h |   3 +
2d8b37
 tools/entry.c     |  99 +++++++++++++++++++++++++++++++++
2d8b37
 tools/tools.c     |   1 +
2d8b37
 tools/tools.h     |   4 ++
2d8b37
 6 files changed, 265 insertions(+)
2d8b37
2d8b37
diff --git a/doc/adcli.xml b/doc/adcli.xml
2d8b37
index 1ed5d3f..6c36297 100644
2d8b37
--- a/doc/adcli.xml
2d8b37
+++ b/doc/adcli.xml
2d8b37
@@ -56,6 +56,11 @@
2d8b37
 		<arg choice="opt">--domain=domain.example.com</arg>
2d8b37
 		<arg choice="plain">user</arg>
2d8b37
 	</cmdsynopsis>
2d8b37
+	<cmdsynopsis>
2d8b37
+		<command>adcli passwd-user</command>
2d8b37
+		<arg choice="opt">--domain=domain.example.com</arg>
2d8b37
+		<arg choice="plain">user</arg>
2d8b37
+	</cmdsynopsis>
2d8b37
 	<cmdsynopsis>
2d8b37
 		<command>adcli create-group</command>
2d8b37
 		<arg choice="opt">--domain=domain.example.com</arg>
2d8b37
@@ -696,6 +701,21 @@ $ adcli delete-user Fry --domain=domain.example.com
2d8b37
 
2d8b37
 </refsect1>
2d8b37
 
2d8b37
+<refsect1 id='passwd_user'>
2d8b37
+	<title>(Re)setting the password of a User with an Administrative Account</title>
2d8b37
+
2d8b37
+	<para><command>adcli passwd-user</command> sets or resets the password
2d8b37
+	of user account. The administrative account used for this operation
2d8b37
+	must have privileges to set a password.</para>
2d8b37
+
2d8b37
+<programlisting>
2d8b37
+$ adcli passwd-user Fry --domain=domain.example.com
2d8b37
+</programlisting>
2d8b37
+
2d8b37
+	<para>The various global options can be used.</para>
2d8b37
+
2d8b37
+</refsect1>
2d8b37
+
2d8b37
 
2d8b37
 <refsect1 id='create_group'>
2d8b37
 	<title>Creating a Group</title>
2d8b37
diff --git a/library/adentry.c b/library/adentry.c
2d8b37
index 13dcaf8..0d9b9af 100644
2d8b37
--- a/library/adentry.c
2d8b37
+++ b/library/adentry.c
2d8b37
@@ -409,6 +409,144 @@ adcli_entry_delete (adcli_entry *entry)
2d8b37
 	return ADCLI_SUCCESS;
2d8b37
 }
2d8b37
 
2d8b37
+static adcli_result
2d8b37
+adcli_entry_ensure_enabled (adcli_entry *entry)
2d8b37
+{
2d8b37
+	adcli_result res;
2d8b37
+	LDAP *ldap;
2d8b37
+	adcli_attrs *attrs;
2d8b37
+	uint32_t uac = 0;
2d8b37
+	char *uac_str;
2d8b37
+	unsigned long attr_val;
2d8b37
+	char *end;
2d8b37
+
2d8b37
+	return_unexpected_if_fail (entry->entry_attrs != NULL);
2d8b37
+
2d8b37
+	ldap = adcli_conn_get_ldap_connection (entry->conn);
2d8b37
+	return_unexpected_if_fail (ldap != NULL);
2d8b37
+
2d8b37
+	uac_str = _adcli_ldap_parse_value (ldap, entry->entry_attrs,
2d8b37
+	                                   "userAccountControl");
2d8b37
+	if (uac_str != NULL) {
2d8b37
+		attr_val = strtoul (uac_str, &end, 10);
2d8b37
+		if (*end != '\0' || attr_val > UINT32_MAX) {
2d8b37
+			_adcli_warn ("Invalid userAccountControl '%s' for %s account in directory: %s, assuming 0",
2d8b37
+			            uac_str, entry->object_class, entry->entry_dn);
2d8b37
+		} else {
2d8b37
+			uac = attr_val;
2d8b37
+		}
2d8b37
+		free (uac_str);
2d8b37
+	}
2d8b37
+	if (uac & UAC_ACCOUNTDISABLE) {
2d8b37
+		uac &= ~(UAC_ACCOUNTDISABLE);
2d8b37
+
2d8b37
+		if (asprintf (&uac_str, "%d", uac) < 0) {
2d8b37
+			_adcli_warn ("Cannot enable %s entry %s after password (re)set",
2d8b37
+			             entry->object_class, entry->entry_dn);
2d8b37
+			return ADCLI_ERR_UNEXPECTED;
2d8b37
+		}
2d8b37
+
2d8b37
+		attrs = adcli_attrs_new ();
2d8b37
+		adcli_attrs_replace (attrs, "userAccountControl", uac_str,
2d8b37
+		                     NULL);
2d8b37
+		res = adcli_entry_modify (entry, attrs);
2d8b37
+		if (res == ADCLI_SUCCESS) {
2d8b37
+			_adcli_info ("Enabled %s entry %s after password (re)set",
2d8b37
+			             entry->object_class, entry->entry_dn);
2d8b37
+		} else {
2d8b37
+			_adcli_warn ("Failed to enable %s entry %s after password (re)set",
2d8b37
+			             entry->object_class, entry->entry_dn);
2d8b37
+		}
2d8b37
+		free (uac_str);
2d8b37
+		adcli_attrs_free (attrs);
2d8b37
+	} else {
2d8b37
+		res = ADCLI_SUCCESS;
2d8b37
+	}
2d8b37
+
2d8b37
+	return res;
2d8b37
+}
2d8b37
+
2d8b37
+adcli_result
2d8b37
+adcli_entry_set_passwd (adcli_entry *entry, const char *user_pwd)
2d8b37
+{
2d8b37
+	adcli_result res;
2d8b37
+	LDAP *ldap;
2d8b37
+	krb5_error_code code;
2d8b37
+	krb5_context k5;
2d8b37
+	krb5_ccache ccache;
2d8b37
+	krb5_data result_string = { 0, };
2d8b37
+	krb5_data result_code_string = { 0, };
2d8b37
+	int result_code;
2d8b37
+	char *message;
2d8b37
+	krb5_principal user_principal;
2d8b37
+
2d8b37
+	ldap = adcli_conn_get_ldap_connection (entry->conn);
2d8b37
+	return_unexpected_if_fail (ldap != NULL);
2d8b37
+
2d8b37
+	/* Find the user */
2d8b37
+	res = update_entry_from_domain (entry, ldap);
2d8b37
+	if (res != ADCLI_SUCCESS)
2d8b37
+		return res;
2d8b37
+
2d8b37
+	if (!entry->entry_dn) {
2d8b37
+		_adcli_err ("Cannot find the %s entry %s in the domain",
2d8b37
+		            entry->object_class, entry->sam_name);
2d8b37
+		return ADCLI_ERR_CONFIG;
2d8b37
+	}
2d8b37
+
2d8b37
+	k5 = adcli_conn_get_krb5_context (entry->conn);
2d8b37
+	return_unexpected_if_fail (k5 != NULL);
2d8b37
+
2d8b37
+	code = _adcli_krb5_build_principal (k5, entry->sam_name,
2d8b37
+	                                    adcli_conn_get_domain_realm (entry->conn),
2d8b37
+	                                    &user_principal);
2d8b37
+	return_unexpected_if_fail (code == 0);
2d8b37
+
2d8b37
+	ccache = adcli_conn_get_login_ccache (entry->conn);
2d8b37
+	return_unexpected_if_fail (ccache != NULL);
2d8b37
+
2d8b37
+	memset (&result_string, 0, sizeof (result_string));
2d8b37
+	memset (&result_code_string, 0, sizeof (result_code_string));
2d8b37
+
2d8b37
+	code = krb5_set_password_using_ccache (k5, ccache, user_pwd,
2d8b37
+	                                       user_principal, &result_code,
2d8b37
+	                                       &result_code_string, &result_string);
2d8b37
+
2d8b37
+	if (code != 0) {
2d8b37
+		_adcli_err ("Couldn't set password for %s account: %s: %s",
2d8b37
+		            entry->object_class,
2d8b37
+		            entry->sam_name, krb5_get_error_message (k5, code));
2d8b37
+		/* TODO: Parse out these values */
2d8b37
+		res = ADCLI_ERR_DIRECTORY;
2d8b37
+
2d8b37
+	} else if (result_code != 0) {
2d8b37
+#ifdef HAVE_KRB5_CHPW_MESSAGE
2d8b37
+		if (krb5_chpw_message (k5, &result_string, &message) != 0)
2d8b37
+			message = NULL;
2d8b37
+#else
2d8b37
+		message = NULL;
2d8b37
+		if (result_string.length)
2d8b37
+			message = _adcli_str_dupn (result_string.data, result_string.length);
2d8b37
+#endif
2d8b37
+		_adcli_err ("Cannot set %s password: %.*s%s%s",
2d8b37
+		            entry->object_class,
2d8b37
+		            (int)result_code_string.length, result_code_string.data,
2d8b37
+		            message ? ": " : "", message ? message : "");
2d8b37
+		res = ADCLI_ERR_CREDENTIALS;
2d8b37
+#ifdef HAVE_KRB5_CHPW_MESSAGE
2d8b37
+		krb5_free_string (k5, message);
2d8b37
+#else
2d8b37
+		free (message);
2d8b37
+#endif
2d8b37
+	} else {
2d8b37
+		_adcli_info ("Password (re)setted for %s: %s", entry->object_class, entry->entry_dn);
2d8b37
+
2d8b37
+		res = adcli_entry_ensure_enabled (entry);
2d8b37
+	}
2d8b37
+
2d8b37
+	return res;
2d8b37
+}
2d8b37
+
2d8b37
 const char *
2d8b37
 adcli_entry_get_sam_name (adcli_entry *entry)
2d8b37
 {
2d8b37
diff --git a/library/adentry.h b/library/adentry.h
2d8b37
index ae90689..f2382b1 100644
2d8b37
--- a/library/adentry.h
2d8b37
+++ b/library/adentry.h
2d8b37
@@ -49,6 +49,9 @@ adcli_result       adcli_entry_modify                   (adcli_entry *entry,
2d8b37
 
2d8b37
 adcli_result       adcli_entry_delete                   (adcli_entry *entry);
2d8b37
 
2d8b37
+adcli_result       adcli_entry_set_passwd               (adcli_entry *entry,
2d8b37
+                                                         const char *user_pwd);
2d8b37
+
2d8b37
 const char *       adcli_entry_get_domain_ou            (adcli_entry *entry);
2d8b37
 
2d8b37
 void               adcli_entry_set_domain_ou            (adcli_entry *entry,
2d8b37
diff --git a/tools/entry.c b/tools/entry.c
2d8b37
index 05e4313..52d2546 100644
2d8b37
--- a/tools/entry.c
2d8b37
+++ b/tools/entry.c
2d8b37
@@ -24,6 +24,7 @@
2d8b37
 #include "config.h"
2d8b37
 
2d8b37
 #include "adcli.h"
2d8b37
+#include "adprivate.h"
2d8b37
 #include "adattrs.h"
2d8b37
 #include "tools.h"
2d8b37
 
2d8b37
@@ -385,6 +386,104 @@ adcli_tool_user_delete (adcli_conn *conn,
2d8b37
 	return 0;
2d8b37
 }
2d8b37
 
2d8b37
+int
2d8b37
+adcli_tool_user_passwd (adcli_conn *conn,
2d8b37
+                        int argc,
2d8b37
+                        char *argv[])
2d8b37
+{
2d8b37
+	adcli_result res;
2d8b37
+	adcli_entry *entry;
2d8b37
+	int opt;
2d8b37
+	char *user_pwd = NULL;
2d8b37
+
2d8b37
+	struct option options[] = {
2d8b37
+		{ "domain", required_argument, NULL, opt_domain },
2d8b37
+		{ "domain-realm", required_argument, NULL, opt_domain_realm },
2d8b37
+		{ "domain-controller", required_argument, NULL, opt_domain_controller },
2d8b37
+		{ "use-ldaps", no_argument, 0, opt_use_ldaps },
2d8b37
+		{ "login-user", required_argument, NULL, opt_login_user },
2d8b37
+		{ "login-ccache", optional_argument, NULL, opt_login_ccache },
2d8b37
+		{ "no-password", no_argument, 0, opt_no_password },
2d8b37
+		{ "stdin-password", no_argument, 0, opt_stdin_password },
2d8b37
+		{ "prompt-password", no_argument, 0, opt_prompt_password },
2d8b37
+		{ "verbose", no_argument, NULL, opt_verbose },
2d8b37
+		{ "help", no_argument, NULL, 'h' },
2d8b37
+		{ 0 },
2d8b37
+	};
2d8b37
+
2d8b37
+	static adcli_tool_desc usages[] = {
2d8b37
+		{ 0, "usage: adcli passwd-user --domain=xxxx user" },
2d8b37
+		{ 0 },
2d8b37
+	};
2d8b37
+
2d8b37
+	while ((opt = adcli_tool_getopt (argc, argv, options)) != -1) {
2d8b37
+		switch (opt) {
2d8b37
+		case 'h':
2d8b37
+		case '?':
2d8b37
+		case ':':
2d8b37
+			adcli_tool_usage (options, usages);
2d8b37
+			adcli_tool_usage (options, common_usages);
2d8b37
+			return opt == 'h' ? 0 : 2;
2d8b37
+		default:
2d8b37
+			res = parse_option ((Option)opt, optarg, conn);
2d8b37
+			if (res != ADCLI_SUCCESS) {
2d8b37
+				return res;
2d8b37
+			}
2d8b37
+			break;
2d8b37
+		}
2d8b37
+	}
2d8b37
+
2d8b37
+	argc -= optind;
2d8b37
+	argv += optind;
2d8b37
+
2d8b37
+	if (argc != 1) {
2d8b37
+		warnx ("specify one user name to (re)set password");
2d8b37
+		return 2;
2d8b37
+	}
2d8b37
+
2d8b37
+	entry = adcli_entry_new_user (conn, argv[0]);
2d8b37
+	if (entry == NULL) {
2d8b37
+		warnx ("unexpected memory problems");
2d8b37
+		return -1;
2d8b37
+	}
2d8b37
+
2d8b37
+	adcli_conn_set_allowed_login_types (conn, ADCLI_LOGIN_USER_ACCOUNT);
2d8b37
+
2d8b37
+	res = adcli_conn_connect (conn);
2d8b37
+	if (res != ADCLI_SUCCESS) {
2d8b37
+		warnx ("couldn't connect to %s domain: %s",
2d8b37
+		       adcli_conn_get_domain_name (conn),
2d8b37
+		       adcli_get_last_error ());
2d8b37
+		adcli_entry_unref (entry);
2d8b37
+		return -res;
2d8b37
+	}
2d8b37
+
2d8b37
+	user_pwd = adcli_prompt_password_func (ADCLI_LOGIN_USER_ACCOUNT,
2d8b37
+	                                       adcli_entry_get_sam_name(entry),
2d8b37
+	                                       0, NULL);
2d8b37
+	if (user_pwd == NULL || *user_pwd == '\0') {
2d8b37
+		warnx ("missing password");
2d8b37
+		_adcli_password_free (user_pwd);
2d8b37
+		adcli_entry_unref (entry);
2d8b37
+		return 2;
2d8b37
+	}
2d8b37
+
2d8b37
+	res = adcli_entry_set_passwd (entry, user_pwd);
2d8b37
+	_adcli_password_free (user_pwd);
2d8b37
+	if (res != ADCLI_SUCCESS) {
2d8b37
+		warnx ("(re)setting password for user %s in domain %s failed: %s",
2d8b37
+		       adcli_entry_get_sam_name (entry),
2d8b37
+		       adcli_conn_get_domain_name (conn),
2d8b37
+		       adcli_get_last_error ());
2d8b37
+		adcli_entry_unref (entry);
2d8b37
+		return -res;
2d8b37
+	}
2d8b37
+
2d8b37
+	adcli_entry_unref (entry);
2d8b37
+
2d8b37
+	return 0;
2d8b37
+}
2d8b37
+
2d8b37
 int
2d8b37
 adcli_tool_group_create (adcli_conn *conn,
2d8b37
                          int argc,
2d8b37
diff --git a/tools/tools.c b/tools/tools.c
2d8b37
index 84bbba9..a14b9ca 100644
2d8b37
--- a/tools/tools.c
2d8b37
+++ b/tools/tools.c
2d8b37
@@ -63,6 +63,7 @@ struct {
2d8b37
 	{ "create-msa", adcli_tool_computer_managed_service_account, "Create a managed service account in the given AD domain", },
2d8b37
 	{ "create-user", adcli_tool_user_create, "Create a user account", },
2d8b37
 	{ "delete-user", adcli_tool_user_delete, "Delete a user account", },
2d8b37
+	{ "passwd-user", adcli_tool_user_passwd, "(Re)set a user password", },
2d8b37
 	{ "create-group", adcli_tool_group_create, "Create a group", },
2d8b37
 	{ "delete-group", adcli_tool_group_delete, "Delete a group", },
2d8b37
 	{ "add-member", adcli_tool_member_add, "Add users to a group", },
2d8b37
diff --git a/tools/tools.h b/tools/tools.h
2d8b37
index 82d5e4e..d38aa32 100644
2d8b37
--- a/tools/tools.h
2d8b37
+++ b/tools/tools.h
2d8b37
@@ -94,6 +94,10 @@ int       adcli_tool_user_delete       (adcli_conn *conn,
2d8b37
                                         int argc,
2d8b37
                                         char *argv[]);
2d8b37
 
2d8b37
+int       adcli_tool_user_passwd       (adcli_conn *conn,
2d8b37
+                                        int argc,
2d8b37
+                                        char *argv[]);
2d8b37
+
2d8b37
 int       adcli_tool_group_create      (adcli_conn *conn,
2d8b37
                                         int argc,
2d8b37
                                         char *argv[]);
2d8b37
-- 
2d8b37
2.31.1
2d8b37