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