static int samldb_service_principal_names_change(struct samldb_ctx *ac)
{
	struct ldb_context *ldb = ldb_module_get_ctx(ac->module);
	struct ldb_message_element *el = NULL, *el2 = NULL;
	struct ldb_message *msg;
	const char * const attrs[] = { "servicePrincipalName", NULL };
	struct ldb_result *res;
	const char *dns_hostname = NULL, *old_dns_hostname = NULL,
		   *sam_accountname = NULL, *old_sam_accountname = NULL;
	unsigned int i, j;
	int ret;

	ret = dsdb_get_expected_new_values(ac,
					   ac->msg,
					   "dNSHostName",
					   &el,
					   ac->req->operation);
	if (ret != LDB_SUCCESS) {
		return ret;
	}
	ret = dsdb_get_expected_new_values(ac,
					   ac->msg,
					   "sAMAccountName",
					   &el2,
					   ac->req->operation);
	if (ret != LDB_SUCCESS) {
		return ret;
	}
	if ((el == NULL) && (el2 == NULL)) {
		/* we are not affected */
		return LDB_SUCCESS;
	}

	/* Create a temporary message for fetching the "dNSHostName" */
	if (el != NULL) {
		const char *dns_attrs[] = { "dNSHostName", NULL };
		msg = ldb_msg_new(ac->msg);
		if (msg == NULL) {
			return ldb_module_oom(ac->module);
		}
		ret = ldb_msg_add(msg, el, 0);
		if (ret != LDB_SUCCESS) {
			return ret;
		}
		dns_hostname = talloc_strdup(ac,
					     ldb_msg_find_attr_as_string(msg, "dNSHostName", NULL));
		if (dns_hostname == NULL) {
			return ldb_module_oom(ac->module);
		}

		talloc_free(msg);

		ret = dsdb_module_search_dn(ac->module, ac, &res, ac->msg->dn,
					    dns_attrs, DSDB_FLAG_NEXT_MODULE, ac->req);
		if (ret == LDB_SUCCESS) {
			old_dns_hostname = ldb_msg_find_attr_as_string(res->msgs[0], "dNSHostName", NULL);
		}
	}

	/* Create a temporary message for fetching the "sAMAccountName" */
	if (el2 != NULL) {
		char *tempstr, *tempstr2 = NULL;
		const char *acct_attrs[] = { "sAMAccountName", NULL };

		msg = ldb_msg_new(ac->msg);
		if (msg == NULL) {
			return ldb_module_oom(ac->module);
		}
		ret = ldb_msg_add(msg, el2, 0);
		if (ret != LDB_SUCCESS) {
			return ret;
		}
		tempstr = talloc_strdup(ac,
					ldb_msg_find_attr_as_string(msg, "sAMAccountName", NULL));
		talloc_free(msg);

		ret = dsdb_module_search_dn(ac->module, ac, &res, ac->msg->dn, acct_attrs,
					    DSDB_FLAG_NEXT_MODULE, ac->req);
		if (ret == LDB_SUCCESS) {
			tempstr2 = talloc_strdup(ac,
						 ldb_msg_find_attr_as_string(res->msgs[0],
									     "sAMAccountName", NULL));
		}


		/* The "sAMAccountName" needs some additional trimming: we need
		 * to remove the trailing "$"s if they exist. */
		if ((tempstr != NULL) && (tempstr[0] != '\0') &&
		    (tempstr[strlen(tempstr) - 1] == '$')) {
			tempstr[strlen(tempstr) - 1] = '\0';
		}
		if ((tempstr2 != NULL) && (tempstr2[0] != '\0') &&
		    (tempstr2[strlen(tempstr2) - 1] == '$')) {
			tempstr2[strlen(tempstr2) - 1] = '\0';
		}
		sam_accountname = tempstr;
		old_sam_accountname = tempstr2;
	}

	if (old_dns_hostname == NULL) {
		/* we cannot change when the old name is unknown */
		dns_hostname = NULL;
	}
	if ((old_dns_hostname != NULL) && (dns_hostname != NULL) &&
	    (strcasecmp_m(old_dns_hostname, dns_hostname) == 0)) {
		/* The "dNSHostName" didn't change */
		dns_hostname = NULL;
	}

	if (old_sam_accountname == NULL) {
		/* we cannot change when the old name is unknown */
		sam_accountname = NULL;
	}
	if ((old_sam_accountname != NULL) && (sam_accountname != NULL) &&
	    (strcasecmp_m(old_sam_accountname, sam_accountname) == 0)) {
		/* The "sAMAccountName" didn't change */
		sam_accountname = NULL;
	}

	if ((dns_hostname == NULL) && (sam_accountname == NULL)) {
		/* Well, there are information missing (old name(s)) or the
		 * names didn't change. We've nothing to do and can exit here */
		return LDB_SUCCESS;
	}

	/*
	 * Potential "servicePrincipalName" changes in the same request have
	 * to be handled before the update (Windows behaviour).
	 *
	 * We extract the SPN changes into a new message and run it through
	 * the stack from this module, so that it subjects them to the SPN
	 * checks we have here.
	 */
	el = ldb_msg_find_element(ac->msg, "servicePrincipalName");
	if (el != NULL) {
		msg = ldb_msg_new(ac->msg);
		if (msg == NULL) {
			return ldb_module_oom(ac->module);
		}
		msg->dn = ac->msg->dn;

		do {
			ret = ldb_msg_add(msg, el, el->flags);
			if (ret != LDB_SUCCESS) {
				return ret;
			}

			ldb_msg_remove_element(ac->msg, el);

			el = ldb_msg_find_element(ac->msg,
						  "servicePrincipalName");
		} while (el != NULL);

		ret = dsdb_module_modify(ac->module, msg,
					 DSDB_FLAG_OWN_MODULE, ac->req);
		if (ret != LDB_SUCCESS) {
			return ret;
		}
		talloc_free(msg);
	}

	/* Fetch the "servicePrincipalName"s if any */
	ret = dsdb_module_search(ac->module, ac, &res, ac->msg->dn, LDB_SCOPE_BASE, attrs,
				 DSDB_FLAG_NEXT_MODULE, ac->req, NULL);
	if (ret != LDB_SUCCESS) {
		return ret;
	}
	if ((res->count != 1) || (res->msgs[0]->num_elements > 1)) {
		return ldb_operr(ldb);
	}

	if (res->msgs[0]->num_elements == 1) {
		/*
		 * Yes, we do have "servicePrincipalName"s. First we update them
		 * locally, that means we do always substitute the current
		 * "dNSHostName" with the new one and/or "sAMAccountName"
		 * without "$" with the new one and then we append the
		 * modified "servicePrincipalName"s as a message element
		 * replace to the modification request (Windows behaviour). We
		 * need also to make sure that the values remain case-
		 * insensitively unique.
		 */

		ret = ldb_msg_add_empty(ac->msg, "servicePrincipalName",
					LDB_FLAG_MOD_REPLACE, &el);
		if (ret != LDB_SUCCESS) {
			return ret;
		}

		for (i = 0; i < res->msgs[0]->elements[0].num_values; i++) {
			char *old_str, *new_str;
			char *pos = NULL;
			const char *tok;
			struct ldb_val *vals;
			bool found = false;

			old_str = (char *)
				res->msgs[0]->elements[0].values[i].data;

			new_str = talloc_strdup(ac->msg,
						strtok_r(old_str, "/", &pos));
			if (new_str == NULL) {
				return ldb_module_oom(ac->module);
			}

			while ((tok = strtok_r(NULL, "/", &pos)) != NULL) {
				if ((dns_hostname != NULL) &&
				    (strcasecmp_m(tok, old_dns_hostname) == 0)) {
					tok = dns_hostname;
				}
				if ((sam_accountname != NULL) &&
				    (strcasecmp_m(tok, old_sam_accountname) == 0)) {
					tok = sam_accountname;
				}

				new_str = talloc_asprintf(ac->msg, "%s/%s",
							  new_str, tok);
				if (new_str == NULL) {
					return ldb_module_oom(ac->module);
				}
			}

			/* Uniqueness check */
			for (j = 0; (!found) && (j < el->num_values); j++) {
				if (strcasecmp_m((char *)el->values[j].data,
					       new_str) == 0) {
					found = true;
				}
			}
			if (found) {
				continue;
			}

			/*
			 * append the new "servicePrincipalName" -
			 * code derived from ldb_msg_add_value().
			 *
			 * Open coded to make it clear that we must
			 * append to the MOD_REPLACE el created above.
			 */
			vals = talloc_realloc(ac->msg, el->values,
					      struct ldb_val,
					      el->num_values + 1);
			if (vals == NULL) {
				return ldb_module_oom(ac->module);
			}
			el->values = vals;
			el->values[el->num_values] = data_blob_string_const(new_str);
			++(el->num_values);
		}
	}

	talloc_free(res);

	return LDB_SUCCESS;
}