diff --git a/0001-tools-add-missing-use-ldaps-option-to-update-and-tes.patch b/0001-tools-add-missing-use-ldaps-option-to-update-and-tes.patch new file mode 100644 index 0000000..13a72a1 --- /dev/null +++ b/0001-tools-add-missing-use-ldaps-option-to-update-and-tes.patch @@ -0,0 +1,37 @@ +From 76ca1e6737742208d83e016d43a3379e378f8d90 Mon Sep 17 00:00:00 2001 +From: Sumit Bose +Date: Wed, 14 Oct 2020 17:44:10 +0200 +Subject: [PATCH 01/10] tools: add missing use-ldaps option to update and + testjoin + +When adding the use-ldaps option the update and testjoin sub-commands +were forgotten. + +Resolves: https://bugzilla.redhat.com/show_bug.cgi?id=1883467 +--- + tools/computer.c | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/tools/computer.c b/tools/computer.c +index 24ea258..5a97d8b 100644 +--- a/tools/computer.c ++++ b/tools/computer.c +@@ -491,6 +491,7 @@ adcli_tool_computer_update (adcli_conn *conn, + struct option options[] = { + { "domain", required_argument, NULL, opt_domain }, + { "domain-controller", required_argument, NULL, opt_domain_controller }, ++ { "use-ldaps", no_argument, 0, opt_use_ldaps }, + { "host-fqdn", required_argument, 0, opt_host_fqdn }, + { "computer-name", required_argument, 0, opt_computer_name }, + { "host-keytab", required_argument, 0, opt_host_keytab }, +@@ -612,6 +613,7 @@ adcli_tool_computer_testjoin (adcli_conn *conn, + struct option options[] = { + { "domain", required_argument, NULL, opt_domain }, + { "domain-controller", required_argument, NULL, opt_domain_controller }, ++ { "use-ldaps", no_argument, 0, opt_use_ldaps }, + { "host-keytab", required_argument, 0, opt_host_keytab }, + { "verbose", no_argument, NULL, opt_verbose }, + { "help", no_argument, NULL, 'h' }, +-- +2.28.0 + diff --git a/0002-join-update-set-dNSHostName-if-not-set.patch b/0002-join-update-set-dNSHostName-if-not-set.patch new file mode 100644 index 0000000..83fc958 --- /dev/null +++ b/0002-join-update-set-dNSHostName-if-not-set.patch @@ -0,0 +1,59 @@ +From beb7abfacc0010987d2cd8ab70f7c373d309eed9 Mon Sep 17 00:00:00 2001 +From: Sumit Bose +Date: Thu, 15 Oct 2020 18:01:12 +0200 +Subject: [PATCH 02/10] join/update: set dNSHostName if not set + +If during a join or update an existing AD computer object does not have +the dNSHostName attribute set it will be set with the current hostname. +This is important for cases where the user doing the join or update only +has "Validated write to service principal name" for the computer object. +The validated write with fully-qualified names can only be successful if +dNSHostName is set, see [MS-ADTS] section 3.1.1.5.3.1.1.4 "Validated +Writes - servicePrincipalName" for details. + +Resolves https://bugzilla.redhat.com/show_bug.cgi?id=1734764 +--- + library/adenroll.c | 16 ++++++++++++---- + 1 file changed, 12 insertions(+), 4 deletions(-) + +diff --git a/library/adenroll.c b/library/adenroll.c +index 246f658..e745295 100644 +--- a/library/adenroll.c ++++ b/library/adenroll.c +@@ -1403,21 +1403,29 @@ update_computer_account (adcli_enroll *enroll) + { + int res = 0; + LDAP *ldap; ++ char *value = NULL; + + ldap = adcli_conn_get_ldap_connection (enroll->conn); + return_if_fail (ldap != NULL); + + /* Only update attributes which are explicitly given on the command +- * line. Otherwise 'adcli update' must be always called with the same +- * set of options to make sure existing attributes are not deleted or +- * overwritten with different values. */ +- if (enroll->host_fqdn_explicit) { ++ * line or not set in the existing AD object. Otherwise 'adcli update' ++ * must be always called with the same set of options to make sure ++ * existing attributes are not deleted or overwritten with different ++ * values. */ ++ if (enroll->computer_attributes != NULL) { ++ value = _adcli_ldap_parse_value (ldap, ++ enroll->computer_attributes, ++ "dNSHostName"); ++ } ++ if (enroll->host_fqdn_explicit || value == NULL ) { + char *vals_dNSHostName[] = { enroll->host_fqdn, NULL }; + LDAPMod dNSHostName = { LDAP_MOD_REPLACE, "dNSHostName", { vals_dNSHostName, } }; + LDAPMod *mods[] = { &dNSHostName, NULL }; + + res |= update_computer_attribute (enroll, ldap, mods); + } ++ free (value); + + if (res == ADCLI_SUCCESS && enroll->trusted_for_delegation_explicit) { + char *vals_userAccountControl[] = { NULL , NULL }; +-- +2.28.0 + diff --git a/0003-doc-explain-required-AD-permissions.patch b/0003-doc-explain-required-AD-permissions.patch new file mode 100644 index 0000000..d6d9024 --- /dev/null +++ b/0003-doc-explain-required-AD-permissions.patch @@ -0,0 +1,242 @@ +From fa5c5fb4f8e7bcadf3e5a3798bd060720fd35eaa Mon Sep 17 00:00:00 2001 +From: Sumit Bose +Date: Tue, 20 Oct 2020 13:34:41 +0200 +Subject: [PATCH 03/10] doc: explain required AD permissions + +When using a restricted account with adcli some operations might fail +because the account might not have all required permissions. The man +page is extended and now explains which permissions are needed under +given circumstances. + +Resolves: https://bugzilla.redhat.com/show_bug.cgi?id=1852080 +Resolves: https://gitlab.freedesktop.org/realmd/adcli/-/issues/20 +--- + doc/Makefile.am | 10 ++++ + doc/adcli.xml | 132 +++++++++++++++++++++++++++++++++++++++++++++ + library/adenroll.c | 30 ++++++----- + 3 files changed, 160 insertions(+), 12 deletions(-) + +diff --git a/doc/Makefile.am b/doc/Makefile.am +index 4490688..50fb777 100644 +--- a/doc/Makefile.am ++++ b/doc/Makefile.am +@@ -33,14 +33,17 @@ EXTRA_DIST = \ + version.xml \ + samba_data_tool_path.xml.in \ + samba_data_tool_path.xml \ ++ permissions.xml \ + $(NULL) + + CLEANFILES = \ + $(man8_MANS) \ ++ permissions.xml \ + $(NULL) + + XSLTPROC_FLAGS = \ + --nonet \ ++ --xinclude \ + --stringparam man.output.quietly 1 \ + --stringparam funcsynopsis.style ansi \ + --stringparam man.th.extra1.suppress 1 \ +@@ -50,6 +53,13 @@ XSLTPROC_FLAGS = \ + XSLTPROC_MAN = \ + $(XSLTPROC) $(XSLTPROC_FLAGS) http://docbook.sourceforge.net/release/xsl/current/manpages/docbook.xsl + ++permissions.xml: ../library/adenroll.c adcli.xml ++ echo "" > $@ ++ grep '".*".*/\* :ADPermissions: ' $< | sed -e 's#.*"\(.*\)".*/\* :ADPermissions: \(.*\)\*/$$#\1\2#' | sed -e 's#\*##g' >> $@ ++ echo "" >> $@ ++ ++$(man8_MANS): permissions.xml ++ + .xml.8: + $(AM_V_GEN) $(XSLTPROC_MAN) $< + +diff --git a/doc/adcli.xml b/doc/adcli.xml +index 1437679..cc44fd8 100644 +--- a/doc/adcli.xml ++++ b/doc/adcli.xml +@@ -885,6 +885,138 @@ Password for Administrator: + + + ++ ++ Delegated Permissions ++ It is common practice in AD to not use an account from the Domain ++ Administrators group to join a machine to a domain but use a dedicated ++ account which only has permissions to join a machine to one or more OUs ++ in the Active Directory tree. Giving the needed permissions to a single ++ account or a group in Active Directory is called Delegation. A typical ++ example on how to configured Delegation can be found in the Delegation ++ section of the blog post ++ Who can add workstation to the domain. ++ ++ ++ When using an account with delegated permissions with adcli ++ basically the same applies as well. However some aspects are explained ++ here in a bit more details to better illustrate different concepts of ++ Active Directory and to make it more easy to debug permissions issues ++ during the join. Please note that the following is not specific to ++ adcli but applies to all applications which would like to modify ++ certain properties or objects in Active Directory with an account with ++ limited permissions. ++ ++ First, as said in the blog post it is sufficient to have ++ "Create computer object" permissions to join a ++ computer to a domain. But this would only work as expected if the ++ computer object does not exist in Active Directory before the join. ++ Because only when a new object is created Active Directory does not ++ apply additional permission checks on the attributes of the new ++ computer object. This means the delegated user can add any kind of ++ attribute with any value to a new computer object also long as they ++ meet general constraints like e.g. that the attribute must be defined ++ in the schema and is allowed in a objectclass of the object, the value ++ must match the syntax defined in the schema or that the ++ must be unique in the domain. ++ ++ If you want to use the account with delegated permission to ++ remove computer objects in Active Directory (adcli delete-computer) you ++ should of course make sure that the account has ++ "Delete computer object" permissions. ++ ++ If the computer object already exists the ++ "Create computer object" permission does not apply ++ anymore since now an existing object must be modified. Now permissions ++ on the individual attributes are needed. e.g. ++ "Read and write Account Restrictions" or ++ "Reset Password". For some attributes Active ++ Directory has two types of permissions the plain ++ "Read and Write" permissions and the ++ "Validated Write" permissions. For the latter case ++ there are two specific permissions relevant for adcli, namely ++ ++ Validated write to DNS host name ++ Validated write to service principal name ++ ++ Details about the validation of the values can be found in the ++ "Validated Writes" section of ++ [MS-ADTS], especially ++ dNSHostName ++ and ++ servicePrincipalName. ++ To cut it short for "Validated write to DNS host name" ++ the domain part of the fully-qualified hostname must either match the ++ domain name of the domain you want to join to or must be listed in the ++ attribute. And for ++ "Validated write to service principal name" the ++ hostname part of the service principal name must match the name stored ++ in or some other attributes which are ++ not handled by adcli. This also means that ++ cannot be empty or only contain a short ++ name if the service principal name should contain a fully-qualified ++ name. ++ ++ To summarize, if you only have validated write permissions you ++ should make sure the domain part of the hostname matches the domain you ++ want to join or use the with a matching ++ name. ++ ++ The plain read write permissions do not run additional ++ validations but the attribute values must still be in agreement with ++ the general constraints mentioned above. If the computer object already ++ exists adcli might need the following permissions which are also needed ++ by Windows clients to modify existing attributes: ++ ++ Reset Password ++ Read and write Account Restrictions ++ Read and (validated) write to DNS host name ++ Read and (validated) write to service principal name ++ ++ additionally adcli needs ++ ++ Read and write msDS-supportedEncryptionTypes ++ ++ This is added for security reasons to avoid that Active Directory ++ stores Kerberos keys with (potentially weaker) encryption types than ++ the client supports since Active Directory is often configured to still ++ support older (weaker) encryption types for compatibility reasons. ++ ++ ++ All other attributes are only set or modified on demand, i.e. ++ adcli must be called with an option the would set or modify the given ++ attribute. In the following the attributes adcli can modify together ++ with the required permissions are listed: ++ ++ ++ ++ For the management of users and groups (adcli create-user, ++ adcli delete-user, adcli create-group, adcli delete-group) the same ++ applies only for different types of objects, i.e. users and groups. ++ Since currently adcli only supports the creation and the removal of ++ user and group objects it is sufficient to have the ++ "Create/Delete User objects" and ++ "Create/Delete Group objects" permissions. ++ ++ If you want to manage group members as well (adcli add-member, ++ adcli remove-member) "Read/Write Members" permissions ++ are needed as well. ++ ++ Depending on the version of Active Directory the ++ "Delegation of Control Wizard" might offer some ++ shortcuts for common task like e.g. ++ ++ Create, delete and manage user accounts ++ Create, delete and manage groups ++ Modify the membership of a group ++ ++ The first 2 shortcuts will provided full access to user and group ++ objects which, as explained above, is more than currently is needed. ++ After using those shortcut it is a good idea to verify in the ++ "Security" tab in the "Properties" ++ of the related Active Directory container that the assigned permissions ++ meet the expectations. ++ ++ + + Bugs + +diff --git a/library/adenroll.c b/library/adenroll.c +index e745295..98e9786 100644 +--- a/library/adenroll.c ++++ b/library/adenroll.c +@@ -71,19 +71,25 @@ static krb5_enctype v51_earlier_enctypes[] = { + 0 + }; + ++/* The following list containst all attributes handled by adcli, some are ++ * read-only and the others can be written as well. To properly document the ++ * required permissions each attribute which adcli tries to modify should have ++ * a comment starting with ':ADPermissions:' and the related permissions in AD ++ * on the same line. Multiple permissions can be seperated with a '*'. For all ++ * other attribute a suitable comment is very welcome. */ + static char *default_ad_ldap_attrs[] = { +- "sAMAccountName", +- "userPrincipalName", +- "msDS-KeyVersionNumber", +- "msDS-supportedEncryptionTypes", +- "dNSHostName", +- "servicePrincipalName", +- "operatingSystem", +- "operatingSystemVersion", +- "operatingSystemServicePack", +- "pwdLastSet", +- "userAccountControl", +- "description", ++ "sAMAccountName", /* Only set during creation */ ++ "userPrincipalName", /* :ADPermissions: Read/Write userPrincipal Name */ ++ "msDS-KeyVersionNumber", /* Manages by AD */ ++ "msDS-supportedEncryptionTypes", /* :ADPermissions: Read/Write msDS-SupportedEncryptionTypes */ ++ "dNSHostName", /* :ADPermissions: Read/Write dNSHostName * Read and write DNS host name attributes * Validated write to DNS host name */ ++ "servicePrincipalName", /* :ADPermissions: Read/Write servicePrincipalName * Validated write to service principal name */ ++ "operatingSystem", /* :ADPermissions: Read/Write Operating System */ ++ "operatingSystemVersion", /* :ADPermissions: Read/Write Operating System Version */ ++ "operatingSystemServicePack", /* :ADPermissions: Read/Write operatingSystemServicePack */ ++ "pwdLastSet", /* Managed by AD */ ++ "userAccountControl", /* :ADPermissions: Read/Write userAccountControl */ ++ "description", /* :ADPermissions: Read/Write Description */ + NULL, + }; + +-- +2.28.0 + diff --git a/0004-enroll-add-is_service-member.patch b/0004-enroll-add-is_service-member.patch new file mode 100644 index 0000000..d4057d9 --- /dev/null +++ b/0004-enroll-add-is_service-member.patch @@ -0,0 +1,66 @@ +From 4e4dbf8d2b437808863f8be85e7f30865d88c7fc Mon Sep 17 00:00:00 2001 +From: Sumit Bose +Date: Fri, 23 Oct 2020 16:46:43 +0200 +Subject: [PATCH 04/10] enroll: add is_service member + +Add helpers to indicate a managed service account. + +Resolves: https://bugzilla.redhat.com/show_bug.cgi?id=1854112 +--- + library/adenroll.c | 17 +++++++++++++++++ + library/adenroll.h | 4 ++++ + 2 files changed, 21 insertions(+) + +diff --git a/library/adenroll.c b/library/adenroll.c +index 98e9786..5ae1f7b 100644 +--- a/library/adenroll.c ++++ b/library/adenroll.c +@@ -103,6 +103,8 @@ static char *default_ad_ldap_attrs[] = { + struct _adcli_enroll { + int refs; + adcli_conn *conn; ++ bool is_service; ++ bool is_service_explicit; + + char *host_fqdn; + int host_fqdn_explicit; +@@ -2942,6 +2944,21 @@ adcli_enroll_get_desciption (adcli_enroll *enroll) + return enroll->description; + } + ++void ++adcli_enroll_set_is_service (adcli_enroll *enroll, bool value) ++{ ++ return_if_fail (enroll != NULL); ++ ++ enroll->is_service = value; ++ enroll->is_service_explicit = true; ++} ++ ++bool ++adcli_enroll_get_is_service (adcli_enroll *enroll) ++{ ++ return enroll->is_service; ++} ++ + const char ** + adcli_enroll_get_service_principals_to_add (adcli_enroll *enroll) + { +diff --git a/library/adenroll.h b/library/adenroll.h +index 0606169..7765ed4 100644 +--- a/library/adenroll.h ++++ b/library/adenroll.h +@@ -130,6 +130,10 @@ const char * adcli_enroll_get_desciption (adcli_enroll *enroll); + void adcli_enroll_set_description (adcli_enroll *enroll, + const char *value); + ++bool adcli_enroll_get_is_service (adcli_enroll *enroll); ++void adcli_enroll_set_is_service (adcli_enroll *enroll, ++ bool value); ++ + krb5_kvno adcli_enroll_get_kvno (adcli_enroll *enroll); + + void adcli_enroll_set_kvno (adcli_enroll *enroll, +-- +2.28.0 + diff --git a/0005-computer-add-create-msa-sub-command.patch b/0005-computer-add-create-msa-sub-command.patch new file mode 100644 index 0000000..552a3ca --- /dev/null +++ b/0005-computer-add-create-msa-sub-command.patch @@ -0,0 +1,646 @@ +From 41379f7ad6a9442dd55cc43d832427911e86db31 Mon Sep 17 00:00:00 2001 +From: Sumit Bose +Date: Fri, 23 Oct 2020 16:53:43 +0200 +Subject: [PATCH 05/10] computer: add create-msa sub-command + +Add new sub-command to create a managed service account in AD. This can +be used if LDAP access to AD is needed but the host is already joined to +a different domain. + +Resolves: https://bugzilla.redhat.com/show_bug.cgi?id=1854112 +--- + doc/adcli.xml | 140 ++++++++++++++++++++++++++++++++++++++ + library/adenroll.c | 164 ++++++++++++++++++++++++++++++++++++++------- + tools/computer.c | 125 ++++++++++++++++++++++++++++++++++ + tools/tools.c | 1 + + tools/tools.h | 4 ++ + 5 files changed, 409 insertions(+), 25 deletions(-) + +diff --git a/doc/adcli.xml b/doc/adcli.xml +index cc44fd8..14921f9 100644 +--- a/doc/adcli.xml ++++ b/doc/adcli.xml +@@ -98,6 +98,10 @@ + --domain=domain.example.com + computer + ++ ++ adcli create-msa ++ --domain=domain.example.com ++ + + + +@@ -885,6 +889,142 @@ Password for Administrator: + + + ++ ++ Create a managed service account ++ ++ adcli create-msa creates a managed service ++ account (MSA) in the given Active Directory domain. This is useful if a ++ computer should not fully join the Active Directory domain but LDAP ++ access is needed. A typical use case is that the computer is already ++ joined an Active Directory domain and needs access to another Active ++ Directory domain in the same or a trusted forest where the host ++ credentials from the joined Active Directory domain are ++ not valid, e.g. there is only a one-way trust. ++ ++ ++$ adcli create-msa --domain=domain.example.com ++Password for Administrator: ++ ++ ++ The managed service account, as maintained by adcli, cannot have ++ additional service principals names (SPNs) associated with it. An SPN ++ is defined within the context of a Kerberos service which is tied to a ++ machine account in Active Directory. Since a machine can be joined to a ++ single Active Directory domain, managed service account in a different ++ Active Directory domain will not have the SPNs that otherwise are part ++ of another Active Directory domain's machine. ++ ++ Since it is expected that a client will most probably join to the ++ Active Directory domain matching its DNS domain the managed service ++ account will be needed for a different Active directory domain and as a ++ result the Active Directory domain name is a mandatory option. If ++ called with no other options adcli create-msa ++ will use the short hostname with an additional random suffix as ++ computer name to avoid name collisions. ++ ++ LDAP attribute sAMAccountName has a limit of 20 characters. ++ However, machine account's NetBIOS name must be at most 16 characters ++ long, including a trailing '$' sign. Since it is not expected that the ++ managed service accounts created by adcli will be used on the NetBIOS ++ level the remaining 4 characters can be used to add uniqueness. Managed ++ service account names will have a suffix of 3 random characters from ++ number and upper- and lowercase ASCII ranges appended to the chosen ++ short host name, using '!' as a separator. For a host with the ++ shortname 'myhost', a managed service account will have a common name ++ (CN attribute) 'myhost!A2c' and a NetBIOS name ++ (sAMAccountName attribute) will be 'myhost!A2c$'. A corresponding ++ Kerberos principal in the Active Directory domain where the managed ++ service account was created would be ++ 'myhost!A2c$@DOMAIN.EXAMPLE.COM'. ++ ++ A keytab for the managed service account is stored into a file ++ specified with -K option. If it is not specified, the file is named ++ after the default keytab file, with lowercase Active Directory domain ++ of the managed service account as a suffix. On most systems it would be ++ /etc/krb5.keytab with a suffix of ++ 'domain.example.com', e.g. ++ /etc/krb5.keytad.domain.example.com. ++ ++ adcli create-msa can be called multiple ++ times to reset the password of the managed service account. To identify ++ the right account with the random component in the name the ++ corresponding principal is read from the keytab. If the keytab got ++ deleted adcli will try to identify an existing ++ managed service account with the help of the fully-qualified name, if ++ this fails a new managed service account will be created. ++ ++ The managed service account password can be updated with ++ ++$ adcli update --domain=domain.example.com --host-keytab=/etc/krb5.keytad.domain.example.com ++ ++ and the managed service account can be deleted with ++ ++$ adcli delete-computer --domain=domain.example.com 'myhost!A2c' ++ ++ ++ ++ In addition to the global options, you can specify the following ++ options to control how this operation is done. ++ ++ ++ ++ ++ The short non-dotted name of the managed ++ service account that will be created in the Active ++ Directory domain. The long option name ++ is ++ kept to underline the similarity with the same option ++ of the other sub-commands. If not specified, ++ then the first portion of the ++ or its default is used with a random suffix. ++ ++ ++ ++ The full distinguished name of the OU in ++ which to create the managed service account. If not ++ specified, then the managed service account will be ++ created in a default location. ++ ++ ++ ++ Override the local machine's fully ++ qualified DNS domain name. If not specified, the local ++ machine's hostname will be retrieved via ++ gethostname(). ++ If gethostname() only returns a short name ++ getaddrinfo() with the AI_CANONNAME hint ++ is called to expand the name to a fully qualified DNS ++ domain name. ++ ++ ++ ++ Specify the path to the host keytab where ++ credentials of the managed service account will be ++ written after a successful creation. If not specified, ++ the default location will be used, usually ++ /etc/krb5.keytab with ++ the lower-cased Active Directory domain name added as a ++ suffix e.g. ++ /etc/krb5.keytab.domain.example.com. ++ ++ ++ ++ ++ After a successful creation print out ++ information about the created object. This is output in ++ a format that should be both human and machine ++ readable. ++ ++ ++ ++ After a successful creation print out ++ the managed service account password. This is output in ++ a format that should be both human and machine ++ readable. ++ ++ ++ ++ + + Delegated Permissions + It is common practice in AD to not use an account from the Domain +diff --git a/library/adenroll.c b/library/adenroll.c +index 5ae1f7b..dbfda36 100644 +--- a/library/adenroll.c ++++ b/library/adenroll.c +@@ -155,6 +155,20 @@ struct _adcli_enroll { + char *description; + }; + ++static void ++check_if_service (adcli_enroll *enroll, ++ LDAP *ldap, ++ LDAPMessage *results) ++{ ++ char **objectclasses = NULL; ++ ++ objectclasses = _adcli_ldap_parse_values (ldap, results, "objectClass"); ++ enroll->is_service = _adcli_strv_has_ex (objectclasses, ++ "msDS-ManagedServiceAccount", ++ strcasecmp) == 1 ? true : false; ++ _adcli_strv_free (objectclasses); ++} ++ + static adcli_result + ensure_host_fqdn (adcli_result res, + adcli_enroll *enroll) +@@ -471,13 +485,15 @@ ensure_keytab_principals (adcli_result res, + { + krb5_context k5; + krb5_error_code code; +- int count; ++ int count = 0; + int at, i; + + /* Prepare the principals we're going to add to the keytab */ + +- return_unexpected_if_fail (enroll->service_principals); +- count = _adcli_strv_len (enroll->service_principals); ++ if (!enroll->is_service) { ++ return_unexpected_if_fail (enroll->service_principals); ++ count = _adcli_strv_len (enroll->service_principals); ++ } + + k5 = adcli_conn_get_krb5_context (enroll->conn); + return_unexpected_if_fail (k5 != NULL); +@@ -556,8 +572,12 @@ static adcli_result + lookup_computer_container (adcli_enroll *enroll, + LDAP *ldap) + { +- char *attrs[] = { "wellKnownObjects", NULL }; +- char *prefix = "B:32:AA312825768811D1ADED00C04FD8D5CD:"; ++ char *attrs[] = { enroll->is_service ? "otherWellKnownObjects" ++ : "wellKnownObjects", NULL }; ++ const char *prefix = enroll->is_service ? "B:32:1EB93889E40C45DF9F0C64D23BBB6237:" ++ : "B:32:AA312825768811D1ADED00C04FD8D5CD:"; ++ const char *filter = enroll->is_service ? "(&(objectClass=container)(cn=Managed Service Accounts))" ++ : "(&(objectClass=container)(cn=Computers))"; + int prefix_len; + LDAPMessage *results; + const char *base; +@@ -586,7 +606,7 @@ lookup_computer_container (adcli_enroll *enroll, + "Couldn't lookup computer container: %s", base); + } + +- values = _adcli_ldap_parse_values (ldap, results, "wellKnownObjects"); ++ values = _adcli_ldap_parse_values (ldap, results, attrs[0]); + ldap_msgfree (results); + + prefix_len = strlen (prefix); +@@ -604,8 +624,7 @@ lookup_computer_container (adcli_enroll *enroll, + + /* Try harder */ + if (!enroll->computer_container) { +- ret = ldap_search_ext_s (ldap, base, LDAP_SCOPE_BASE, +- "(&(objectClass=container)(cn=Computers))", ++ ret = ldap_search_ext_s (ldap, base, LDAP_SCOPE_BASE, filter, + attrs, 0, NULL, NULL, NULL, -1, &results); + if (ret == LDAP_SUCCESS) { + enroll->computer_container = _adcli_ldap_parse_dn (ldap, results); +@@ -747,7 +766,7 @@ static adcli_result + create_computer_account (adcli_enroll *enroll, + LDAP *ldap) + { +- char *vals_objectClass[] = { "computer", NULL }; ++ char *vals_objectClass[] = { enroll->is_service ? "msDS-ManagedServiceAccount" : "computer", NULL }; + LDAPMod objectClass = { LDAP_MOD_ADD, "objectClass", { vals_objectClass, } }; + char *vals_sAMAccountName[] = { enroll->computer_sam, NULL }; + LDAPMod sAMAccountName = { LDAP_MOD_ADD, "sAMAccountName", { vals_sAMAccountName, } }; +@@ -806,7 +825,7 @@ create_computer_account (adcli_enroll *enroll, + m = 0; + for (c = 0; c < mods_count - 1; c++) { + /* Skip empty LDAP sttributes */ +- if (all_mods[c]->mod_vals.modv_strvals[0] != NULL) { ++ if (all_mods[c]->mod_vals.modv_strvals != NULL && all_mods[c]->mod_vals.modv_strvals[0] != NULL) { + mods[m++] = all_mods[c]; + } + } +@@ -936,7 +955,7 @@ locate_computer_account (adcli_enroll *enroll, + LDAPMessage **rresults, + LDAPMessage **rentry) + { +- char *attrs[] = { "1.1", NULL }; ++ char *attrs[] = { "objectClass", NULL }; + LDAPMessage *results = NULL; + LDAPMessage *entry = NULL; + const char *base; +@@ -948,7 +967,9 @@ locate_computer_account (adcli_enroll *enroll, + /* If we don't yet know our computer dn, then try and find it */ + value = _adcli_ldap_escape_filter (enroll->computer_sam); + return_unexpected_if_fail (value != NULL); +- if (asprintf (&filter, "(&(objectClass=computer)(sAMAccountName=%s))", value) < 0) ++ if (asprintf (&filter, "(&(objectClass=%s)(sAMAccountName=%s))", ++ enroll->is_service ? "msDS-ManagedServiceAccount" : "computer", ++ value) < 0) + return_unexpected_if_reached (); + free (value); + +@@ -962,8 +983,11 @@ locate_computer_account (adcli_enroll *enroll, + if (ret == LDAP_SUCCESS) { + entry = ldap_first_entry (ldap, results); + +- /* If we found a computer account, make note of dn */ ++ /* If we found a computer/service account, make note of dn */ + if (entry) { ++ if (!enroll->is_service_explicit) { ++ check_if_service ( enroll, ldap, results); ++ } + dn = ldap_get_dn (ldap, entry); + free (enroll->computer_dn); + enroll->computer_dn = strdup (dn); +@@ -1003,7 +1027,7 @@ load_computer_account (adcli_enroll *enroll, + LDAPMessage **rresults, + LDAPMessage **rentry) + { +- char *attrs[] = { "1.1", NULL }; ++ char *attrs[] = { "objectClass", NULL }; + LDAPMessage *results = NULL; + LDAPMessage *entry = NULL; + int ret; +@@ -1081,6 +1105,12 @@ locate_or_create_computer_account (adcli_enroll *enroll, + if (res == ADCLI_SUCCESS && entry == NULL) + res = create_computer_account (enroll, ldap); + ++ /* Service account already exists, just continue and update the ++ * password */ ++ if (enroll->is_service && entry != NULL) { ++ res = ADCLI_SUCCESS; ++ } ++ + if (results) + ldap_msgfree (results); + +@@ -1413,6 +1443,11 @@ update_computer_account (adcli_enroll *enroll) + LDAP *ldap; + char *value = NULL; + ++ /* No updates for service accounts */ ++ if (enroll->is_service) { ++ return; ++ } ++ + ldap = adcli_conn_get_ldap_connection (enroll->conn); + return_if_fail (ldap != NULL); + +@@ -1501,6 +1536,11 @@ update_service_principals (adcli_enroll *enroll) + LDAP *ldap; + int ret; + ++ /* No updates for service accounts */ ++ if (enroll->is_service) { ++ return ADCLI_SUCCESS; ++ } ++ + ldap = adcli_conn_get_ldap_connection (enroll->conn); + return_unexpected_if_fail (ldap != NULL); + +@@ -1614,6 +1654,8 @@ load_keytab_entry (krb5_context k5, + enroll->computer_name = name; + name[len - 1] = '\0'; + _adcli_info ("Found computer name in keytab: %s", name); ++ adcli_conn_set_computer_name (enroll->conn, ++ enroll->computer_name); + name = NULL; + + } else if (!enroll->host_fqdn && _adcli_str_has_prefix (name, "host/") && strchr (name, '.')) { +@@ -2002,17 +2044,25 @@ adcli_enroll_prepare (adcli_enroll *enroll, + + adcli_clear_last_error (); + +- /* Basic discovery and figuring out enroll params */ +- res = ensure_host_fqdn (res, enroll); +- res = ensure_computer_name (res, enroll); +- res = ensure_computer_sam (res, enroll); +- res = ensure_user_principal (res, enroll); +- res = ensure_computer_password (res, enroll); +- if (!(flags & ADCLI_ENROLL_NO_KEYTAB)) ++ if (enroll->is_service) { ++ /* Ensure basic params for service accounts */ ++ res = ensure_computer_sam (res, enroll); ++ res = ensure_computer_password (res, enroll); + res = ensure_host_keytab (res, enroll); +- res = ensure_service_names (res, enroll); +- res = ensure_service_principals (res, enroll); +- res = ensure_keytab_principals (res, enroll); ++ res = ensure_keytab_principals (res, enroll); ++ } else { ++ /* Basic discovery and figuring out enroll params */ ++ res = ensure_host_fqdn (res, enroll); ++ res = ensure_computer_name (res, enroll); ++ res = ensure_computer_sam (res, enroll); ++ res = ensure_user_principal (res, enroll); ++ res = ensure_computer_password (res, enroll); ++ if (!(flags & ADCLI_ENROLL_NO_KEYTAB)) ++ res = ensure_host_keytab (res, enroll); ++ res = ensure_service_names (res, enroll); ++ res = ensure_service_principals (res, enroll); ++ res = ensure_keytab_principals (res, enroll); ++ } + + return res; + } +@@ -2157,6 +2207,58 @@ enroll_join_or_update_tasks (adcli_enroll *enroll, + return update_keytab_for_principals (enroll, flags); + } + ++static adcli_result ++adcli_enroll_add_description_for_service_account (adcli_enroll *enroll) ++{ ++ const char *fqdn; ++ char *desc; ++ ++ fqdn = adcli_conn_get_host_fqdn (enroll->conn); ++ return_unexpected_if_fail (fqdn != NULL); ++ if (asprintf (&desc, "Please do not edit, Service account for %s, " ++ "managed by adcli.", fqdn) < 0) { ++ return_unexpected_if_reached (); ++ } ++ ++ adcli_enroll_set_description (enroll, desc); ++ free (desc); ++ ++ return ADCLI_SUCCESS; ++} ++ ++static adcli_result ++adcli_enroll_add_keytab_for_service_account (adcli_enroll *enroll) ++{ ++ krb5_context k5; ++ krb5_error_code code; ++ char def_keytab_name[MAX_KEYTAB_NAME_LEN]; ++ char *lc_dom_name; ++ int ret; ++ ++ if (adcli_enroll_get_keytab_name (enroll) == NULL) { ++ k5 = adcli_conn_get_krb5_context (enroll->conn); ++ return_unexpected_if_fail (k5 != NULL); ++ ++ code = krb5_kt_default_name (k5, def_keytab_name, ++ sizeof (def_keytab_name)); ++ return_unexpected_if_fail (code == 0); ++ ++ lc_dom_name = strdup (adcli_conn_get_domain_name (enroll->conn)); ++ return_unexpected_if_fail (lc_dom_name != NULL); ++ _adcli_str_down (lc_dom_name); ++ ++ ++ ret = asprintf (&enroll->keytab_name, "%s.%s", def_keytab_name, ++ lc_dom_name); ++ free (lc_dom_name); ++ return_unexpected_if_fail (ret > 0); ++ } ++ ++ _adcli_info ("Using service account keytab: %s", enroll->keytab_name); ++ ++ return ADCLI_SUCCESS; ++} ++ + adcli_result + adcli_enroll_join (adcli_enroll *enroll, + adcli_enroll_flags flags) +@@ -2172,7 +2274,14 @@ adcli_enroll_join (adcli_enroll *enroll, + if (res != ADCLI_SUCCESS) + return res; + +- res = ensure_default_service_names (enroll); ++ if (enroll->is_service) { ++ res = adcli_enroll_add_description_for_service_account (enroll); ++ if (res == ADCLI_SUCCESS) { ++ res = adcli_enroll_add_keytab_for_service_account (enroll); ++ } ++ } else { ++ res = ensure_default_service_names (enroll); ++ } + if (res != ADCLI_SUCCESS) + return res; + +@@ -2281,6 +2390,11 @@ adcli_enroll_update (adcli_enroll *enroll, + } + free (value); + ++ /* We only support password changes for service accounts */ ++ if (enroll->is_service && (flags & ADCLI_ENROLL_PASSWORD_VALID)) { ++ return ADCLI_SUCCESS; ++ } ++ + return enroll_join_or_update_tasks (enroll, flags); + } + +diff --git a/tools/computer.c b/tools/computer.c +index 5a97d8b..63fd374 100644 +--- a/tools/computer.c ++++ b/tools/computer.c +@@ -1074,3 +1074,128 @@ adcli_tool_computer_show (adcli_conn *conn, + adcli_enroll_unref (enroll); + return 0; + } ++ ++int ++adcli_tool_computer_managed_service_account (adcli_conn *conn, ++ int argc, ++ char *argv[]) ++{ ++ adcli_enroll *enroll; ++ adcli_result res; ++ int show_password = 0; ++ int details = 0; ++ int opt; ++ ++ struct option options[] = { ++ { "domain", required_argument, NULL, opt_domain }, ++ { "domain-realm", required_argument, NULL, opt_domain_realm }, ++ { "domain-controller", required_argument, NULL, opt_domain_controller }, ++ { "use-ldaps", no_argument, 0, opt_use_ldaps }, ++ { "login-user", required_argument, NULL, opt_login_user }, ++ { "login-ccache", optional_argument, NULL, opt_login_ccache }, ++ { "host-fqdn", required_argument, 0, opt_host_fqdn }, ++ { "computer-name", required_argument, 0, opt_computer_name }, ++ { "host-keytab", required_argument, 0, opt_host_keytab }, ++ { "no-password", no_argument, 0, opt_no_password }, ++ { "stdin-password", no_argument, 0, opt_stdin_password }, ++ { "prompt-password", no_argument, 0, opt_prompt_password }, ++ { "domain-ou", required_argument, NULL, opt_domain_ou }, ++ { "show-details", no_argument, NULL, opt_show_details }, ++ { "show-password", no_argument, NULL, opt_show_password }, ++ { "verbose", no_argument, NULL, opt_verbose }, ++ { "help", no_argument, NULL, 'h' }, ++ { 0 }, ++ }; ++ ++ static adcli_tool_desc usages[] = { ++ { 0, "usage: adcli create-msa --domain=xxxx" }, ++ { 0 }, ++ }; ++ ++ enroll = adcli_enroll_new (conn); ++ if (enroll == NULL) { ++ warnx ("unexpected memory problems"); ++ return -1; ++ } ++ ++ while ((opt = adcli_tool_getopt (argc, argv, options)) != -1) { ++ switch (opt) { ++ case opt_one_time_password: ++ adcli_conn_set_allowed_login_types (conn, ADCLI_LOGIN_COMPUTER_ACCOUNT); ++ adcli_conn_set_computer_password (conn, optarg); ++ break; ++ case opt_show_details: ++ details = 1; ++ break; ++ case opt_show_password: ++ show_password = 1; ++ break; ++ case 'h': ++ case '?': ++ case ':': ++ adcli_tool_usage (options, usages); ++ adcli_tool_usage (options, common_usages); ++ adcli_enroll_unref (enroll); ++ return opt == 'h' ? 0 : 2; ++ default: ++ res = parse_option ((Option)opt, optarg, conn, enroll); ++ if (res != ADCLI_SUCCESS) { ++ adcli_enroll_unref (enroll); ++ return res; ++ } ++ break; ++ } ++ } ++ ++ argc -= optind; ++ argv += optind; ++ ++ if (argc == 1) ++ adcli_conn_set_domain_name (conn, argv[0]); ++ else if (argc > 1) { ++ warnx ("extra arguments specified"); ++ adcli_enroll_unref (enroll); ++ return 2; ++ } ++ ++ if (adcli_conn_get_domain_name (conn) == NULL) { ++ warnx ("domain name is required"); ++ adcli_enroll_unref (enroll); ++ return 2; ++ } ++ ++ adcli_enroll_set_is_service (enroll, true); ++ adcli_conn_set_allowed_login_types (conn, ADCLI_LOGIN_USER_ACCOUNT); ++ ++ res = adcli_enroll_load (enroll); ++ if (res != ADCLI_SUCCESS) { ++ /* ignored */ ++ } ++ ++ res = adcli_conn_connect (conn); ++ if (res != ADCLI_SUCCESS) { ++ warnx ("couldn't connect to %s domain: %s", ++ adcli_conn_get_domain_name (conn), ++ adcli_get_last_error ()); ++ adcli_enroll_unref (enroll); ++ return -res; ++ } ++ ++ res = adcli_enroll_join (enroll, 0); ++ if (res != ADCLI_SUCCESS) { ++ warnx ("Adding service account for %s failed: %s", ++ adcli_conn_get_domain_name (conn), ++ adcli_get_last_error ()); ++ adcli_enroll_unref (enroll); ++ return -res; ++ } ++ ++ if (details) ++ dump_details (conn, enroll, show_password); ++ else if (show_password) ++ dump_password (conn, enroll); ++ ++ adcli_enroll_unref (enroll); ++ ++ return 0; ++} +diff --git a/tools/tools.c b/tools/tools.c +index 1b6d879..d0dcf98 100644 +--- a/tools/tools.c ++++ b/tools/tools.c +@@ -60,6 +60,7 @@ struct { + { "reset-computer", adcli_tool_computer_reset, "Reset a computer account", }, + { "delete-computer", adcli_tool_computer_delete, "Delete a computer account", }, + { "show-computer", adcli_tool_computer_show, "Show computer account attributes stored in AD", }, ++ { "create-msa", adcli_tool_computer_managed_service_account, "Create a managed service account in the given AD domain", }, + { "create-user", adcli_tool_user_create, "Create a user account", }, + { "delete-user", adcli_tool_user_delete, "Delete a user account", }, + { "create-group", adcli_tool_group_create, "Create a group", }, +diff --git a/tools/tools.h b/tools/tools.h +index 3702875..82d5e4e 100644 +--- a/tools/tools.h ++++ b/tools/tools.h +@@ -82,6 +82,10 @@ int adcli_tool_computer_show (adcli_conn *conn, + int argc, + char *argv[]); + ++int adcli_tool_computer_managed_service_account (adcli_conn *conn, ++ int argc, ++ char *argv[]); ++ + int adcli_tool_user_create (adcli_conn *conn, + int argc, + char *argv[]); +-- +2.28.0 + diff --git a/0006-enroll-use-computer-or-service-in-debug-messages.patch b/0006-enroll-use-computer-or-service-in-debug-messages.patch new file mode 100644 index 0000000..3b41636 --- /dev/null +++ b/0006-enroll-use-computer-or-service-in-debug-messages.patch @@ -0,0 +1,358 @@ +From eea6a8071b5e5df74808903bb15b30acf820ce3f Mon Sep 17 00:00:00 2001 +From: Sumit Bose +Date: Fri, 23 Oct 2020 16:55:11 +0200 +Subject: [PATCH 06/10] enroll: use 'computer' or 'service' in debug messages + +Use proper account type in debug messages. + +Resolves: https://bugzilla.redhat.com/show_bug.cgi?id=1854112 +--- + library/adenroll.c | 115 ++++++++++++++++++++++++++++----------------- + 1 file changed, 72 insertions(+), 43 deletions(-) + +diff --git a/library/adenroll.c b/library/adenroll.c +index dbfda36..9cdc79b 100644 +--- a/library/adenroll.c ++++ b/library/adenroll.c +@@ -155,6 +155,12 @@ struct _adcli_enroll { + char *description; + }; + ++static const char * ++s_or_c (adcli_enroll *enroll) ++{ ++ return enroll->is_service ? "service" : "computer"; ++} ++ + static void + check_if_service (adcli_enroll *enroll, + LDAP *ldap, +@@ -203,13 +209,15 @@ ensure_computer_name (adcli_result res, + return res; + + if (enroll->computer_name) { +- _adcli_info ("Enrolling computer name: %s", ++ _adcli_info ("Enrolling %s name: %s", ++ s_or_c (enroll), + enroll->computer_name); + return ADCLI_SUCCESS; + } + + if (!enroll->host_fqdn) { +- _adcli_err ("No host name from which to determine the computer name"); ++ _adcli_err ("No host name from which to determine the %s name", ++ s_or_c (enroll)); + return ADCLI_ERR_CONFIG; + } + +@@ -603,7 +611,8 @@ lookup_computer_container (adcli_enroll *enroll, + + } else if (ret != LDAP_SUCCESS) { + return _adcli_ldap_handle_failure (ldap, ADCLI_ERR_DIRECTORY, +- "Couldn't lookup computer container: %s", base); ++ "Couldn't lookup %s container: %s", ++ s_or_c (enroll), base); + } + + values = _adcli_ldap_parse_values (ldap, results, attrs[0]); +@@ -614,8 +623,8 @@ lookup_computer_container (adcli_enroll *enroll, + if (strncmp (values[i], prefix, prefix_len) == 0) { + enroll->computer_container = strdup (values[i] + prefix_len); + return_unexpected_if_fail (enroll->computer_container != NULL); +- _adcli_info ("Found well known computer container at: %s", +- enroll->computer_container); ++ _adcli_info ("Found well known %s container at: %s", ++ s_or_c (enroll), enroll->computer_container); + break; + } + } +@@ -629,8 +638,9 @@ lookup_computer_container (adcli_enroll *enroll, + if (ret == LDAP_SUCCESS) { + enroll->computer_container = _adcli_ldap_parse_dn (ldap, results); + if (enroll->computer_container) { +- _adcli_info ("Well known computer container not " ++ _adcli_info ("Well known %s container not " + "found, but found suitable one at: %s", ++ s_or_c (enroll), + enroll->computer_container); + } + } +@@ -646,7 +656,8 @@ lookup_computer_container (adcli_enroll *enroll, + } + + if (!enroll->computer_container) { +- _adcli_err ("Couldn't find location to create computer accounts"); ++ _adcli_err ("Couldn't find location to create %s accounts", ++ s_or_c (enroll)); + return ADCLI_ERR_DIRECTORY; + } + +@@ -674,7 +685,8 @@ calculate_computer_account (adcli_enroll *enroll, + if (asprintf (&enroll->computer_dn, "CN=%s,%s", enroll->computer_name, enroll->computer_container) < 0) + return_unexpected_if_reached (); + +- _adcli_info ("Calculated computer account: %s", enroll->computer_dn); ++ _adcli_info ("Calculated %s account: %s", ++ s_or_c (enroll), enroll->computer_dn); + return ADCLI_SUCCESS; + } + +@@ -861,7 +873,8 @@ create_computer_account (adcli_enroll *enroll, + enroll->computer_dn); + } + +- _adcli_info ("Created computer account: %s", enroll->computer_dn); ++ _adcli_info ("Created %s account: %s", s_or_c (enroll), ++ enroll->computer_dn); + return ADCLI_SUCCESS; + } + +@@ -908,17 +921,17 @@ validate_computer_account (adcli_enroll *enroll, + assert (enroll->computer_dn != NULL); + + if (already_exists && !allow_overwrite) { +- _adcli_err ("The computer account %s already exists", +- enroll->computer_name); ++ _adcli_err ("The %s account %s already exists", ++ s_or_c (enroll), enroll->computer_name); + return ADCLI_ERR_CONFIG; + } + + /* Do we have an explicitly requested ou? */ + if (enroll->domain_ou && enroll->domain_ou_explicit && already_exists) { + if (!_adcli_ldap_dn_has_ancestor (enroll->computer_dn, enroll->domain_ou)) { +- _adcli_err ("The computer account %s already exists, " ++ _adcli_err ("The %s account %s already exists, " + "but is not in the desired organizational unit.", +- enroll->computer_name); ++ s_or_c (enroll), enroll->computer_name); + return ADCLI_ERR_CONFIG; + } + } +@@ -943,7 +956,8 @@ delete_computer_account (adcli_enroll *enroll, + "Couldn't delete computer account: %s", + enroll->computer_dn); + } else { +- _adcli_info ("Deleted computer account at: %s", enroll->computer_dn); ++ _adcli_info ("Deleted %s account at: %s", s_or_c (enroll), ++ enroll->computer_dn); + } + + return ADCLI_SUCCESS; +@@ -992,20 +1006,21 @@ locate_computer_account (adcli_enroll *enroll, + free (enroll->computer_dn); + enroll->computer_dn = strdup (dn); + return_unexpected_if_fail (enroll->computer_dn != NULL); +- _adcli_info ("Found computer account for %s at: %s", +- enroll->computer_sam, dn); ++ _adcli_info ("Found %s account for %s at: %s", ++ s_or_c (enroll), enroll->computer_sam, dn); + ldap_memfree (dn); + + } else { + ldap_msgfree (results); + results = NULL; +- _adcli_info ("Computer account for %s does not exist", +- enroll->computer_sam); ++ _adcli_info ("A %s account for %s does not exist", ++ s_or_c (enroll), enroll->computer_sam); + } + + } else { + return _adcli_ldap_handle_failure (ldap, ADCLI_ERR_DIRECTORY, +- "Couldn't lookup computer account: %s", ++ "Couldn't lookup %s account: %s", ++ s_or_c (enroll), + enroll->computer_sam); + } + +@@ -1039,7 +1054,9 @@ load_computer_account (adcli_enroll *enroll, + if (ret == LDAP_SUCCESS) { + entry = ldap_first_entry (ldap, results); + if (entry) { +- _adcli_info ("Found computer account for %s at: %s", ++ check_if_service (enroll, ldap, results); ++ _adcli_info ("Found %s account for %s at: %s", ++ s_or_c (enroll), + enroll->computer_sam, enroll->computer_dn); + } + +@@ -1146,7 +1163,8 @@ set_password_with_user_creds (adcli_enroll *enroll) + &result_code_string, &result_string); + + if (code != 0) { +- _adcli_err ("Couldn't set password for computer account: %s: %s", ++ _adcli_err ("Couldn't set password for %s account: %s: %s", ++ s_or_c (enroll), + enroll->computer_sam, krb5_get_error_message (k5, code)); + /* TODO: Parse out these values */ + res = ADCLI_ERR_DIRECTORY; +@@ -1160,7 +1178,8 @@ set_password_with_user_creds (adcli_enroll *enroll) + if (result_string.length) + message = _adcli_str_dupn (result_string.data, result_string.length); + #endif +- _adcli_err ("Cannot set computer password: %.*s%s%s", ++ _adcli_err ("Cannot set %s password: %.*s%s%s", ++ s_or_c (enroll), + (int)result_code_string.length, result_code_string.data, + message ? ": " : "", message ? message : ""); + res = ADCLI_ERR_CREDENTIALS; +@@ -1170,7 +1189,7 @@ set_password_with_user_creds (adcli_enroll *enroll) + free (message); + #endif + } else { +- _adcli_info ("Set computer password"); ++ _adcli_info ("Set %s password", s_or_c (enroll)); + if (enroll->kvno > 0) { + enroll->kvno++; + _adcli_info ("kvno incremented to %d", enroll->kvno); +@@ -1203,7 +1222,8 @@ set_password_with_computer_creds (adcli_enroll *enroll) + + code = _adcli_kinit_computer_creds (enroll->conn, "kadmin/changepw", NULL, &creds); + if (code != 0) { +- _adcli_err ("Couldn't get change password ticket for computer account: %s: %s", ++ _adcli_err ("Couldn't get change password ticket for %s account: %s: %s", ++ s_or_c (enroll), + enroll->computer_sam, krb5_get_error_message (k5, code)); + return ADCLI_ERR_DIRECTORY; + } +@@ -1214,7 +1234,8 @@ set_password_with_computer_creds (adcli_enroll *enroll) + krb5_free_cred_contents (k5, &creds); + + if (code != 0) { +- _adcli_err ("Couldn't change password for computer account: %s: %s", ++ _adcli_err ("Couldn't change password for %s account: %s: %s", ++ s_or_c (enroll), + enroll->computer_sam, krb5_get_error_message (k5, code)); + /* TODO: Parse out these values */ + res = ADCLI_ERR_DIRECTORY; +@@ -1284,7 +1305,8 @@ retrieve_computer_account (adcli_enroll *enroll) + + if (ret != LDAP_SUCCESS) { + return _adcli_ldap_handle_failure (ldap, ADCLI_ERR_DIRECTORY, +- "Couldn't retrieve computer account info: %s", ++ "Couldn't retrieve %s account info: %s", ++ s_or_c (enroll), + enroll->computer_dn); + } + +@@ -1294,15 +1316,15 @@ retrieve_computer_account (adcli_enroll *enroll) + if (value != NULL) { + kvno = strtoul (value, &end, 10); + if (end == NULL || *end != '\0') { +- _adcli_err ("Invalid kvno '%s' for computer account in directory: %s", +- value, enroll->computer_dn); ++ _adcli_err ("Invalid kvno '%s' for %s account in directory: %s", ++ value, s_or_c (enroll), enroll->computer_dn); + res = ADCLI_ERR_DIRECTORY; + + } else { + enroll->kvno = kvno; + +- _adcli_info ("Retrieved kvno '%s' for computer account in directory: %s", +- value, enroll->computer_dn); ++ _adcli_info ("Retrieved kvno '%s' for %s account in directory: %s", ++ value, s_or_c (enroll), enroll->computer_dn); + } + + free (value); +@@ -1311,8 +1333,8 @@ retrieve_computer_account (adcli_enroll *enroll) + /* Apparently old AD didn't have this attribute, use zero */ + enroll->kvno = 0; + +- _adcli_info ("No kvno found for computer account in directory: %s", +- enroll->computer_dn); ++ _adcli_info ("No kvno found for %s account in directory: %s", ++ s_or_c (enroll), enroll->computer_dn); + } + } + +@@ -1353,12 +1375,14 @@ update_and_calculate_enctypes (adcli_enroll *enroll) + + if (ret == LDAP_INSUFFICIENT_ACCESS) { + return _adcli_ldap_handle_failure (ldap, ADCLI_ERR_CREDENTIALS, +- "Insufficient permissions to set encryption types on computer account: %s", ++ "Insufficient permissions to set encryption types on %s account: %s", ++ s_or_c (enroll), + enroll->computer_dn); + + } else if (ret != LDAP_SUCCESS) { + return _adcli_ldap_handle_failure (ldap, ADCLI_ERR_DIRECTORY, +- "Couldn't set encryption types on computer account: %s", ++ "Couldn't set encryption types on %s account: %s", ++ s_or_c (enroll), + enroll->computer_dn); + } + +@@ -1381,13 +1405,14 @@ update_computer_attribute (adcli_enroll *enroll, + string = _adcli_ldap_mods_to_string (mods); + return_unexpected_if_fail (string != NULL); + +- _adcli_info ("Modifying computer account: %s", string); ++ _adcli_info ("Modifying %s account: %s", s_or_c (enroll), string); + + ret = ldap_modify_ext_s (ldap, enroll->computer_dn, mods, NULL, NULL); + + if (ret != LDAP_SUCCESS) { +- _adcli_warn ("Couldn't set %s on computer account: %s: %s", +- string, enroll->computer_dn, ldap_err2string (ret)); ++ _adcli_warn ("Couldn't set %s on %s account: %s: %s", ++ string, s_or_c (enroll), enroll->computer_dn, ++ ldap_err2string (ret)); + res = ADCLI_ERR_DIRECTORY; + } + +@@ -1411,8 +1436,8 @@ static char *get_user_account_control (adcli_enroll *enroll) + + attr_val = strtoul (uac_str, &end, 10); + if (*end != '\0' || attr_val > UINT32_MAX) { +- _adcli_warn ("Invalid userAccountControl '%s' for computer account in directory: %s, assuming 0", +- uac_str, enroll->computer_dn); ++ _adcli_warn ("Invalid userAccountControl '%s' for %s account in directory: %s, assuming 0", ++ uac_str, s_or_c (enroll), enroll->computer_dn); + } else { + uac = attr_val; + } +@@ -1653,7 +1678,8 @@ load_keytab_entry (krb5_context k5, + _adcli_str_has_suffix (name, "$") && !strchr (name, '/')) { + enroll->computer_name = name; + name[len - 1] = '\0'; +- _adcli_info ("Found computer name in keytab: %s", name); ++ _adcli_info ("Found %s name in keytab: %s", ++ s_or_c (enroll), name); + adcli_conn_set_computer_name (enroll->conn, + enroll->computer_name); + name = NULL; +@@ -2348,7 +2374,8 @@ adcli_enroll_read_computer_account (adcli_enroll *enroll, + if (res != ADCLI_SUCCESS) + return res; + if (!enroll->computer_dn) { +- _adcli_err ("No computer account for %s exists", enroll->computer_sam); ++ _adcli_err ("No %s account for %s exists", ++ s_or_c (enroll), enroll->computer_sam); + return ADCLI_ERR_CONFIG; + } + } +@@ -2460,7 +2487,8 @@ adcli_enroll_delete (adcli_enroll *enroll, + if (res != ADCLI_SUCCESS) + return res; + if (!enroll->computer_dn) { +- _adcli_err ("No computer account for %s exists", ++ _adcli_err ("No %s account for %s exists", ++ s_or_c (enroll), + enroll->computer_sam); + return ADCLI_ERR_CONFIG; + } +@@ -2503,7 +2531,8 @@ adcli_enroll_password (adcli_enroll *enroll, + if (res != ADCLI_SUCCESS) + return res; + if (!enroll->computer_dn) { +- _adcli_err ("No computer account for %s exists", ++ _adcli_err ("No %s account for %s exists", ++ s_or_c (enroll), + enroll->computer_sam); + return ADCLI_ERR_CONFIG; + } +-- +2.28.0 + diff --git a/0007-enroll-more-filters-for-random-characters.patch b/0007-enroll-more-filters-for-random-characters.patch new file mode 100644 index 0000000..b2ff629 --- /dev/null +++ b/0007-enroll-more-filters-for-random-characters.patch @@ -0,0 +1,77 @@ +From 2750f536ac6746756335eec8332060d2365a4126 Mon Sep 17 00:00:00 2001 +From: Sumit Bose +Date: Tue, 27 Oct 2020 14:44:07 +0100 +Subject: [PATCH 07/10] enroll: more filters for random characters + +Make handling of random strings more flexible. + +Resolves: https://bugzilla.redhat.com/show_bug.cgi?id=1854112 +--- + library/adenroll.c | 30 +++++++++++++++++++++++++++--- + 1 file changed, 27 insertions(+), 3 deletions(-) + +diff --git a/library/adenroll.c b/library/adenroll.c +index 9cdc79b..44383cc 100644 +--- a/library/adenroll.c ++++ b/library/adenroll.c +@@ -259,6 +259,29 @@ ensure_computer_sam (adcli_result res, + return ADCLI_SUCCESS; + } + ++typedef int (rand_filter) (char *password, int length); ++ ++static int ++filter_sam_chars (char *password, ++ int length) ++{ ++ int i, j; ++ ++ /* ++ * There are a couple of restrictions for characters in the ++ * sAMAccountName attribute value, for our purpose (random suffix) ++ * letters and numbers are sufficient. ++ */ ++ for (i = 0, j = 0; i < length; i++) { ++ if (password[i] >= 48 && password[i] <= 122 && ++ isalnum (password[i])) ++ password[j++] = password[i]; ++ } ++ ++ /* return the number of valid characters remaining */ ++ return j; ++} ++ + static int + filter_password_chars (char *password, + int length) +@@ -283,7 +306,8 @@ filter_password_chars (char *password, + + static char * + generate_host_password (adcli_enroll *enroll, +- size_t length) ++ size_t length, ++ rand_filter *filter) + { + char *password; + krb5_context k5; +@@ -305,7 +329,7 @@ generate_host_password (adcli_enroll *enroll, + code = krb5_c_random_make_octets (k5, &buffer); + return_val_if_fail (code == 0, NULL); + +- at += filter_password_chars (buffer.data, buffer.length); ++ at += filter (buffer.data, buffer.length); + assert (at <= length); + } + +@@ -333,7 +357,7 @@ ensure_computer_password (adcli_result res, + _adcli_info ("Using default reset computer password"); + + } else { +- enroll->computer_password = generate_host_password (enroll, length); ++ enroll->computer_password = generate_host_password (enroll, length, filter_password_chars); + return_unexpected_if_fail (enroll->computer_password != NULL); + _adcli_info ("Generated %d character computer password", length); + } +-- +2.28.0 + diff --git a/0008-enroll-make-adcli_enroll_add_keytab_for_service_acco.patch b/0008-enroll-make-adcli_enroll_add_keytab_for_service_acco.patch new file mode 100644 index 0000000..9279f07 --- /dev/null +++ b/0008-enroll-make-adcli_enroll_add_keytab_for_service_acco.patch @@ -0,0 +1,91 @@ +From 81c98e367ba4bc8d77668acd31e462ad31cf12be Mon Sep 17 00:00:00 2001 +From: Sumit Bose +Date: Tue, 27 Oct 2020 14:47:31 +0100 +Subject: [PATCH 08/10] enroll: make + adcli_enroll_add_keytab_for_service_account public + +Determine keytab name more early to catch errors more early. + +Resolves: https://bugzilla.redhat.com/show_bug.cgi?id=1854112 +--- + library/adenroll.c | 13 +++++++------ + library/adenroll.h | 2 ++ + tools/computer.c | 6 ++++++ + 3 files changed, 15 insertions(+), 6 deletions(-) + +diff --git a/library/adenroll.c b/library/adenroll.c +index 44383cc..05bb085 100644 +--- a/library/adenroll.c ++++ b/library/adenroll.c +@@ -2276,9 +2276,10 @@ adcli_enroll_add_description_for_service_account (adcli_enroll *enroll) + return ADCLI_SUCCESS; + } + +-static adcli_result ++adcli_result + adcli_enroll_add_keytab_for_service_account (adcli_enroll *enroll) + { ++ adcli_result res; + krb5_context k5; + krb5_error_code code; + char def_keytab_name[MAX_KEYTAB_NAME_LEN]; +@@ -2286,11 +2287,14 @@ adcli_enroll_add_keytab_for_service_account (adcli_enroll *enroll) + int ret; + + if (adcli_enroll_get_keytab_name (enroll) == NULL) { +- k5 = adcli_conn_get_krb5_context (enroll->conn); +- return_unexpected_if_fail (k5 != NULL); ++ res = _adcli_krb5_init_context (&k5); ++ if (res != ADCLI_SUCCESS) { ++ return res; ++ } + + code = krb5_kt_default_name (k5, def_keytab_name, + sizeof (def_keytab_name)); ++ krb5_free_context (k5); + return_unexpected_if_fail (code == 0); + + lc_dom_name = strdup (adcli_conn_get_domain_name (enroll->conn)); +@@ -2326,9 +2330,6 @@ adcli_enroll_join (adcli_enroll *enroll, + + if (enroll->is_service) { + res = adcli_enroll_add_description_for_service_account (enroll); +- if (res == ADCLI_SUCCESS) { +- res = adcli_enroll_add_keytab_for_service_account (enroll); +- } + } else { + res = ensure_default_service_names (enroll); + } +diff --git a/library/adenroll.h b/library/adenroll.h +index 7765ed4..11a30c8 100644 +--- a/library/adenroll.h ++++ b/library/adenroll.h +@@ -146,6 +146,8 @@ const char * adcli_enroll_get_keytab_name (adcli_enroll *enroll); + void adcli_enroll_set_keytab_name (adcli_enroll *enroll, + const char *value); + ++adcli_result adcli_enroll_add_keytab_for_service_account (adcli_enroll *enroll); ++ + krb5_enctype * adcli_enroll_get_keytab_enctypes (adcli_enroll *enroll); + + void adcli_enroll_set_keytab_enctypes (adcli_enroll *enroll, +diff --git a/tools/computer.c b/tools/computer.c +index 63fd374..98a0472 100644 +--- a/tools/computer.c ++++ b/tools/computer.c +@@ -1166,6 +1166,12 @@ adcli_tool_computer_managed_service_account (adcli_conn *conn, + + adcli_enroll_set_is_service (enroll, true); + adcli_conn_set_allowed_login_types (conn, ADCLI_LOGIN_USER_ACCOUNT); ++ res = adcli_enroll_add_keytab_for_service_account (enroll); ++ if (res != ADCLI_SUCCESS) { ++ warnx ("Failed to set domain specific keytab name"); ++ adcli_enroll_unref (enroll); ++ return 2; ++ } + + res = adcli_enroll_load (enroll); + if (res != ADCLI_SUCCESS) { +-- +2.28.0 + diff --git a/0009-enroll-allow-fqdn-for-locate_computer_account.patch b/0009-enroll-allow-fqdn-for-locate_computer_account.patch new file mode 100644 index 0000000..a35e050 --- /dev/null +++ b/0009-enroll-allow-fqdn-for-locate_computer_account.patch @@ -0,0 +1,129 @@ +From 2a695dfe09cafeee3a648d3b969c364f8d3f494f Mon Sep 17 00:00:00 2001 +From: Sumit Bose +Date: Tue, 27 Oct 2020 14:49:55 +0100 +Subject: [PATCH 09/10] enroll: allow fqdn for locate_computer_account + +Make it possible to find existing manages service account by the +fully-qualified name. + +Resolves: https://bugzilla.redhat.com/show_bug.cgi?id=1854112 +--- + library/adenroll.c | 45 +++++++++++++++++++++++++++++++-------------- + 1 file changed, 31 insertions(+), 14 deletions(-) + +diff --git a/library/adenroll.c b/library/adenroll.c +index 05bb085..98cd5fa 100644 +--- a/library/adenroll.c ++++ b/library/adenroll.c +@@ -990,10 +990,11 @@ delete_computer_account (adcli_enroll *enroll, + static adcli_result + locate_computer_account (adcli_enroll *enroll, + LDAP *ldap, ++ bool use_fqdn, + LDAPMessage **rresults, + LDAPMessage **rentry) + { +- char *attrs[] = { "objectClass", NULL }; ++ char *attrs[] = { "objectClass", "CN", NULL }; + LDAPMessage *results = NULL; + LDAPMessage *entry = NULL; + const char *base; +@@ -1003,12 +1004,22 @@ locate_computer_account (adcli_enroll *enroll, + int ret = 0; + + /* If we don't yet know our computer dn, then try and find it */ +- value = _adcli_ldap_escape_filter (enroll->computer_sam); +- return_unexpected_if_fail (value != NULL); +- if (asprintf (&filter, "(&(objectClass=%s)(sAMAccountName=%s))", +- enroll->is_service ? "msDS-ManagedServiceAccount" : "computer", +- value) < 0) +- return_unexpected_if_reached (); ++ if (use_fqdn) { ++ return_unexpected_if_fail (enroll->host_fqdn != NULL); ++ value = _adcli_ldap_escape_filter (enroll->host_fqdn); ++ return_unexpected_if_fail (value != NULL); ++ if (asprintf (&filter, "(&(objectClass=%s)(dNSHostName=%s))", ++ enroll->is_service ? "msDS-ManagedServiceAccount" : "computer", ++ value) < 0) ++ return_unexpected_if_reached (); ++ } else { ++ value = _adcli_ldap_escape_filter (enroll->computer_sam); ++ return_unexpected_if_fail (value != NULL); ++ if (asprintf (&filter, "(&(objectClass=%s)(sAMAccountName=%s))", ++ enroll->is_service ? "msDS-ManagedServiceAccount" : "computer", ++ value) < 0) ++ return_unexpected_if_reached (); ++ } + free (value); + + base = adcli_conn_get_default_naming_context (enroll->conn); +@@ -1031,21 +1042,26 @@ locate_computer_account (adcli_enroll *enroll, + enroll->computer_dn = strdup (dn); + return_unexpected_if_fail (enroll->computer_dn != NULL); + _adcli_info ("Found %s account for %s at: %s", +- s_or_c (enroll), enroll->computer_sam, dn); ++ s_or_c (enroll), ++ use_fqdn ? enroll->host_fqdn ++ : enroll->computer_sam, dn); + ldap_memfree (dn); + + } else { + ldap_msgfree (results); + results = NULL; + _adcli_info ("A %s account for %s does not exist", +- s_or_c (enroll), enroll->computer_sam); ++ s_or_c (enroll), ++ use_fqdn ? enroll->host_fqdn ++ : enroll->computer_sam); + } + + } else { + return _adcli_ldap_handle_failure (ldap, ADCLI_ERR_DIRECTORY, + "Couldn't lookup %s account: %s", + s_or_c (enroll), +- enroll->computer_sam); ++ use_fqdn ? enroll->host_fqdn ++ :enroll->computer_sam); + } + + if (rresults) +@@ -1120,7 +1136,8 @@ locate_or_create_computer_account (adcli_enroll *enroll, + + /* Try to find the computer account */ + if (!enroll->computer_dn) { +- res = locate_computer_account (enroll, ldap, &results, &entry); ++ res = locate_computer_account (enroll, ldap, false, ++ &results, &entry); + if (res != ADCLI_SUCCESS) + return res; + searched = 1; +@@ -2395,7 +2412,7 @@ adcli_enroll_read_computer_account (adcli_enroll *enroll, + + /* Find the computer dn */ + if (!enroll->computer_dn) { +- res = locate_computer_account (enroll, ldap, NULL, NULL); ++ res = locate_computer_account (enroll, ldap, false, NULL, NULL); + if (res != ADCLI_SUCCESS) + return res; + if (!enroll->computer_dn) { +@@ -2508,7 +2525,7 @@ adcli_enroll_delete (adcli_enroll *enroll, + + /* Find the computer dn */ + if (!enroll->computer_dn) { +- res = locate_computer_account (enroll, ldap, NULL, NULL); ++ res = locate_computer_account (enroll, ldap, false, NULL, NULL); + if (res != ADCLI_SUCCESS) + return res; + if (!enroll->computer_dn) { +@@ -2552,7 +2569,7 @@ adcli_enroll_password (adcli_enroll *enroll, + + /* Find the computer dn */ + if (!enroll->computer_dn) { +- res = locate_computer_account (enroll, ldap, NULL, NULL); ++ res = locate_computer_account (enroll, ldap, false, NULL, NULL); + if (res != ADCLI_SUCCESS) + return res; + if (!enroll->computer_dn) { +-- +2.28.0 + diff --git a/0010-service-account-add-random-suffix-to-account-name.patch b/0010-service-account-add-random-suffix-to-account-name.patch new file mode 100644 index 0000000..74a93fb --- /dev/null +++ b/0010-service-account-add-random-suffix-to-account-name.patch @@ -0,0 +1,122 @@ +From 6b94f9712378b8f1fa1bc530c64cb987abb0c43b Mon Sep 17 00:00:00 2001 +From: Sumit Bose +Date: Tue, 27 Oct 2020 15:23:04 +0100 +Subject: [PATCH 10/10] service-account: add random suffix to account name + +Add a random component to the default managed service account name to +avoid name collisions. + +Resolves: https://bugzilla.redhat.com/show_bug.cgi?id=1854112 +--- + library/adenroll.c | 79 ++++++++++++++++++++++++++++++++++++++++++++++ + 1 file changed, 79 insertions(+) + +diff --git a/library/adenroll.c b/library/adenroll.c +index 98cd5fa..f693e58 100644 +--- a/library/adenroll.c ++++ b/library/adenroll.c +@@ -1121,6 +1121,59 @@ load_computer_account (adcli_enroll *enroll, + return ADCLI_SUCCESS; + } + ++static adcli_result ++refresh_service_account_name_sam_and_princ (adcli_enroll *enroll, ++ const char *name) ++{ ++ adcli_result res; ++ ++ adcli_enroll_set_computer_name (enroll, name); ++ res = ensure_computer_sam (ADCLI_SUCCESS, enroll); ++ res = ensure_keytab_principals (res, enroll); ++ ++ return res; ++} ++ ++static adcli_result ++calculate_random_service_account_name (adcli_enroll *enroll) ++{ ++ char *suffix; ++ char *new_name; ++ int ret; ++ adcli_result res; ++ ++ suffix = generate_host_password (enroll, 3, filter_sam_chars); ++ return_unexpected_if_fail (suffix != NULL); ++ ++ ret = asprintf (&new_name, "%s!%s", enroll->computer_name, suffix); ++ free (suffix); ++ return_unexpected_if_fail (ret > 0); ++ ++ res = refresh_service_account_name_sam_and_princ (enroll, new_name); ++ free (new_name); ++ ++ return res; ++} ++ ++static adcli_result ++get_service_account_name_from_ldap (adcli_enroll *enroll, LDAPMessage *results) ++{ ++ LDAP *ldap; ++ char *cn; ++ adcli_result res; ++ ++ ldap = adcli_conn_get_ldap_connection (enroll->conn); ++ assert (ldap != NULL); ++ ++ cn = _adcli_ldap_parse_value (ldap, results, "CN"); ++ return_unexpected_if_fail (cn != NULL); ++ ++ res = refresh_service_account_name_sam_and_princ (enroll, cn); ++ free (cn); ++ ++ return res; ++} ++ + static adcli_result + locate_or_create_computer_account (adcli_enroll *enroll, + int allow_overwrite) +@@ -1143,8 +1196,32 @@ locate_or_create_computer_account (adcli_enroll *enroll, + searched = 1; + } + ++ /* Try with fqdn for service accounts */ ++ if (!enroll->computer_dn && enroll->is_service ++ && enroll->host_fqdn != NULL) { ++ res = locate_computer_account (enroll, ldap, true, ++ &results, &entry); ++ if (res != ADCLI_SUCCESS) ++ return res; ++ searched = 1; ++ ++ if (results != NULL) { ++ res = get_service_account_name_from_ldap (enroll, ++ results); ++ if (res != ADCLI_SUCCESS) { ++ return res; ++ } ++ } ++ } ++ + /* Next try and come up with where we think it should be */ + if (enroll->computer_dn == NULL) { ++ if (enroll->is_service && !enroll->computer_name_explicit) { ++ res = calculate_random_service_account_name (enroll); ++ if (res != ADCLI_SUCCESS) { ++ return res; ++ } ++ } + res = calculate_computer_account (enroll, ldap); + if (res != ADCLI_SUCCESS) + return res; +@@ -2113,6 +2190,8 @@ adcli_enroll_prepare (adcli_enroll *enroll, + + if (enroll->is_service) { + /* Ensure basic params for service accounts */ ++ res = ensure_host_fqdn (res, enroll); ++ res = ensure_computer_name (res, enroll); + res = ensure_computer_sam (res, enroll); + res = ensure_computer_password (res, enroll); + res = ensure_host_keytab (res, enroll); +-- +2.28.0 + diff --git a/adcli.spec b/adcli.spec index f489b8c..4dd73fc 100644 --- a/adcli.spec +++ b/adcli.spec @@ -1,6 +1,6 @@ Name: adcli Version: 0.9.0 -Release: 5%{?dist} +Release: 6%{?dist} Summary: Active Directory enrollment License: LGPLv2+ URL: http://cgit.freedesktop.org/realmd/adcli @@ -18,6 +18,16 @@ Patch9: 0001-tools-fix-typo-in-show-password-help-output.patch Patch10: 0002-man-explain-optional-parameter-of-login-ccache-bette.patch Patch11: 0003-man-make-handling-of-optional-credential-cache-more-.patch +Patch12: 0001-tools-add-missing-use-ldaps-option-to-update-and-tes.patch +Patch13: 0002-join-update-set-dNSHostName-if-not-set.patch +Patch14: 0003-doc-explain-required-AD-permissions.patch +Patch15: 0004-enroll-add-is_service-member.patch +Patch16: 0005-computer-add-create-msa-sub-command.patch +Patch17: 0006-enroll-use-computer-or-service-in-debug-messages.patch +Patch18: 0007-enroll-more-filters-for-random-characters.patch +Patch19: 0008-enroll-make-adcli_enroll_add_keytab_for_service_acco.patch +Patch20: 0009-enroll-allow-fqdn-for-locate_computer_account.patch +Patch21: 0010-service-account-add-random-suffix-to-account-name.patch BuildRequires: gcc BuildRequires: intltool pkgconfig @@ -75,6 +85,10 @@ documentation. %doc %{_datadir}/doc/adcli/* %changelog +* Fri Nov 13 2020 Sumit Bose - 0.9.0-6 +- Include the latest upstream patches with use-ldaps fixes, man page + improvements and a new sub-command to create managed service accounts + * Thu Aug 13 2020 Sumit Bose - 0.9.0-5 - man page and help output fixes