TALLOC_FREE(frame);
return werr;
}
+
+/**
+ * @brief Update a PAC
+ *
+ * @param mem_ctx A talloc memory context
+ *
+ * @param context A krb5 context
+ *
+ * @param samdb An open samdb connection.
+ *
+ * @param flags Bitwise OR'ed flags
+ *
+ * @param client The client samba kdc entry.
+
+ * @param server_principal The server principal
+
+ * @param server The server samba kdc entry.
+
+ * @param krbtgt The krbtgt samba kdc entry.
+ *
+ * @param delegated_proxy_principal The delegated proxy principal used for
+ * updating the constrained delegation PAC
+ * buffer.
+
+ * @param old_pac The old PAC
+
+ * @param new_pac The new already allocated PAC
+
+ * @return A Kerberos error code. If no PAC should be returned, the code will be
+ * ENODATA!
+ */
+krb5_error_code samba_kdc_update_pac(TALLOC_CTX *mem_ctx,
+ krb5_context context,
+ struct ldb_context *samdb,
+ uint32_t flags,
+ struct samba_kdc_entry *client,
+ const krb5_principal server_principal,
+ struct samba_kdc_entry *server,
+ struct samba_kdc_entry *krbtgt,
+ const krb5_principal delegated_proxy_principal,
+ const krb5_pac old_pac,
+ krb5_pac new_pac)
+{
+ krb5_error_code code = EINVAL;
+ NTSTATUS nt_status;
+ DATA_BLOB *pac_blob = NULL;
+ DATA_BLOB *upn_blob = NULL;
+ DATA_BLOB *deleg_blob = NULL;
+ DATA_BLOB *requester_sid_blob = NULL;
+ bool is_untrusted = flags & SAMBA_KDC_FLAG_KRBTGT_IS_UNTRUSTED;
+ int is_tgs = false;
+ size_t num_types = 0;
+ uint32_t *types = NULL;
+ /*
+ * FIXME: Do we really still need forced_next_type? With MIT Kerberos
+ * the PAC buffers do not get ordered and it works just fine. We are
+ * not aware of any issues in this regard. This might be just ancient
+ * code.
+ */
+ uint32_t forced_next_type = 0;
+ size_t i = 0;
+ ssize_t logon_info_idx = -1;
+ ssize_t delegation_idx = -1;
+ ssize_t logon_name_idx = -1;
+ ssize_t upn_dns_info_idx = -1;
+ ssize_t srv_checksum_idx = -1;
+ ssize_t kdc_checksum_idx = -1;
+ ssize_t tkt_checksum_idx = -1;
+ ssize_t attrs_info_idx = -1;
+ ssize_t requester_sid_idx = -1;
+
+ if (client != NULL) {
+ /*
+ * Check the objectSID of the client and pac data are the same.
+ * Does a parse and SID check, but no crypto.
+ */
+ code = samba_kdc_validate_pac_blob(context,
+ client,
+ old_pac);
+ if (code != 0) {
+ goto done;
+ }
+ }
+
+ if (delegated_proxy_principal != NULL) {
+ deleg_blob = talloc_zero(mem_ctx, DATA_BLOB);
+ if (deleg_blob == NULL) {
+ code = ENOMEM;
+ goto done;
+ }
+
+ nt_status = samba_kdc_update_delegation_info_blob(
+ mem_ctx,
+ context,
+ old_pac,
+ server_principal,
+ delegated_proxy_principal,
+ deleg_blob);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ DBG_ERR("update delegation info blob failed: %s\n",
+ nt_errstr(nt_status));
+ code = EINVAL;
+ goto done;
+ }
+ }
+
+ if (is_untrusted) {
+ struct auth_user_info_dc *user_info_dc = NULL;
+ WERROR werr;
+
+ if (client == NULL) {
+ code = KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN;
+ goto done;
+ }
+
+ nt_status = samba_kdc_get_pac_blobs(mem_ctx,
+ client,
+ &pac_blob,
+ NULL,
+ &upn_blob,
+ NULL,
+ PAC_ATTRIBUTE_FLAG_PAC_WAS_GIVEN_IMPLICITLY,
+ &requester_sid_blob,
+ &user_info_dc);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ DBG_ERR("samba_kdc_get_pac_blobs failed: %s\n",
+ nt_errstr(nt_status));
+ code = KRB5KDC_ERR_TGT_REVOKED;
+ goto done;
+ }
+
+ /*
+ * Check if the SID list in the user_info_dc intersects
+ * correctly with the RODC allow/deny lists.
+ */
+ werr = samba_rodc_confirm_user_is_allowed(user_info_dc->num_sids,
+ user_info_dc->sids,
+ krbtgt,
+ client);
+ TALLOC_FREE(user_info_dc);
+ if (!W_ERROR_IS_OK(werr)) {
+ code = KRB5KDC_ERR_TGT_REVOKED;
+ if (W_ERROR_EQUAL(werr,
+ WERR_DOMAIN_CONTROLLER_NOT_FOUND)) {
+ code = KRB5KDC_ERR_POLICY;
+ }
+ goto done;
+ }
+ } else {
+ pac_blob = talloc_zero(mem_ctx, DATA_BLOB);
+ if (pac_blob == NULL) {
+ code = ENOMEM;
+ goto done;
+ }
+
+ nt_status = samba_kdc_update_pac_blob(mem_ctx,
+ context,
+ samdb,
+ old_pac,
+ pac_blob,
+ NULL,
+ NULL);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ DBG_ERR("samba_kdc_update_pac_blob failed: %s\n",
+ nt_errstr(nt_status));
+ code = EINVAL;
+ goto done;
+ }
+ }
+
+ /* Check the types of the given PAC */
+ code = krb5_pac_get_types(context, old_pac, &num_types, &types);
+ if (code != 0) {
+ DBG_ERR("krb5_pac_get_types failed\n");
+ goto done;
+ }
+
+ for (i = 0; i < num_types; i++) {
+ switch (types[i]) {
+ case PAC_TYPE_LOGON_INFO:
+ if (logon_info_idx != -1) {
+ DBG_WARNING("logon info type[%u] twice [%zd] "
+ "and [%zu]: \n",
+ types[i],
+ logon_info_idx,
+ i);
+ code = EINVAL;
+ goto done;
+ }
+ logon_info_idx = i;
+ break;
+ case PAC_TYPE_CONSTRAINED_DELEGATION:
+ if (delegation_idx != -1) {
+ DBG_WARNING("constrained delegation type[%u] "
+ "twice [%zd] and [%zu]: \n",
+ types[i],
+ delegation_idx,
+ i);
+ code = EINVAL;
+ goto done;
+ }
+ delegation_idx = i;
+ break;
+ case PAC_TYPE_LOGON_NAME:
+ if (logon_name_idx != -1) {
+ DBG_WARNING("logon name type[%u] twice [%zd] "
+ "and [%zu]: \n",
+ types[i],
+ logon_name_idx,
+ i);
+ code = EINVAL;
+ goto done;
+ }
+ logon_name_idx = i;
+ break;
+ case PAC_TYPE_UPN_DNS_INFO:
+ if (upn_dns_info_idx != -1) {
+ DBG_WARNING("upn dns info type[%u] twice [%zd] "
+ "and [%zu]: \n",
+ types[i],
+ upn_dns_info_idx,
+ i);
+ code = EINVAL;
+ goto done;
+ }
+ upn_dns_info_idx = i;
+ break;
+ case PAC_TYPE_SRV_CHECKSUM:
+ if (srv_checksum_idx != -1) {
+ DBG_WARNING("srv checksum type[%u] twice [%zd] "
+ "and [%zu]: \n",
+ types[i],
+ srv_checksum_idx,
+ i);
+ code = EINVAL;
+ goto done;
+ }
+ srv_checksum_idx = i;
+ break;
+ case PAC_TYPE_KDC_CHECKSUM:
+ if (kdc_checksum_idx != -1) {
+ DBG_WARNING("kdc checksum type[%u] twice [%zd] "
+ "and [%zu]: \n",
+ types[i],
+ kdc_checksum_idx,
+ i);
+ code = EINVAL;
+ goto done;
+ }
+ kdc_checksum_idx = i;
+ break;
+ case PAC_TYPE_TICKET_CHECKSUM:
+ if (tkt_checksum_idx != -1) {
+ DBG_WARNING("ticket checksum type[%u] twice "
+ "[%zd] and [%zu]: \n",
+ types[i],
+ tkt_checksum_idx,
+ i);
+ code = EINVAL;
+ goto done;
+ }
+ tkt_checksum_idx = i;
+ break;
+ case PAC_TYPE_ATTRIBUTES_INFO:
+ if (attrs_info_idx != -1) {
+ DBG_WARNING("attributes info type[%u] twice "
+ "[%zd] and [%zu]: \n",
+ types[i],
+ attrs_info_idx,
+ i);
+ code = EINVAL;
+ goto done;
+ }
+ attrs_info_idx = i;
+ break;
+ case PAC_TYPE_REQUESTER_SID:
+ if (requester_sid_idx != -1) {
+ DBG_WARNING("requester sid type[%u] twice"
+ "[%zd] and [%zu]: \n",
+ types[i],
+ requester_sid_idx,
+ i);
+ code = EINVAL;
+ goto done;
+ }
+ requester_sid_idx = i;
+ break;
+ default:
+ continue;
+ }
+ }
+
+ if (logon_info_idx == -1) {
+ DBG_WARNING("PAC_TYPE_LOGON_INFO missing\n");
+ code = EINVAL;
+ goto done;
+ }
+ if (logon_name_idx == -1) {
+ DBG_WARNING("PAC_TYPE_LOGON_NAME missing\n");
+ code = EINVAL;
+ goto done;
+ }
+ if (srv_checksum_idx == -1) {
+ DBG_WARNING("PAC_TYPE_SRV_CHECKSUM missing\n");
+ code = EINVAL;
+ goto done;
+ }
+ if (kdc_checksum_idx == -1) {
+ DBG_WARNING("PAC_TYPE_KDC_CHECKSUM missing\n");
+ code = EINVAL;
+ goto done;
+ }
+ if (!(flags & SAMBA_KDC_FLAG_CONSTRAINED_DELEGATION) &&
+ requester_sid_idx == -1) {
+ DBG_WARNING("PAC_TYPE_REQUESTER_SID missing\n");
+ code = KRB5KDC_ERR_TGT_REVOKED;
+ goto done;
+ }
+
+ /*
+ * The server account may be set not to want the PAC.
+ *
+ * While this is wasteful if the above cacluations were done
+ * and now thrown away, this is cleaner as we do any ticket
+ * signature checking etc always.
+ *
+ * UF_NO_AUTH_DATA_REQUIRED is the rare case and most of the
+ * time (eg not accepting a ticket from the RODC) we do not
+ * need to re-generate anything anyway.
+ */
+ if (!samba_princ_needs_pac(server)) {
+ code = ENODATA;
+ goto done;
+ }
+
+ is_tgs = smb_krb5_principal_is_tgs(context, server_principal);
+ if (is_tgs == -1) {
+ code = ENOMEM;
+ goto done;
+ }
+
+ if (!is_untrusted && !is_tgs) {
+ /*
+ * The client may have requested no PAC when obtaining the
+ * TGT.
+ */
+ bool requested_pac = false;
+
+ code = samba_client_requested_pac(context,
+ &old_pac,
+ mem_ctx,
+ &requested_pac);
+ if (code != 0 || !requested_pac) {
+ if (!requested_pac) {
+ code = ENODATA;
+ }
+ goto done;
+ }
+ }
+
+#define MAX_PAC_BUFFERS 128 /* Avoid infinite loops */
+
+ for (i = 0; i < MAX_PAC_BUFFERS;) {
+ const uint8_t zero_byte = 0;
+ krb5_data type_data;
+ DATA_BLOB type_blob = data_blob_null;
+ uint32_t type;
+
+ if (forced_next_type != 0) {
+ /*
+ * We need to inject possible missing types
+ */
+ type = forced_next_type;
+ forced_next_type = 0;
+ } else if (i < num_types) {
+ type = types[i];
+ i++;
+ } else {
+ break;
+ }
+
+ switch (type) {
+ case PAC_TYPE_LOGON_INFO:
+ type_blob = *pac_blob;
+
+ if (delegation_idx == -1 && deleg_blob != NULL) {
+ /* inject CONSTRAINED_DELEGATION behind */
+ forced_next_type =
+ PAC_TYPE_CONSTRAINED_DELEGATION;
+ }
+ break;
+ case PAC_TYPE_CONSTRAINED_DELEGATION:
+ /*
+ * This is generated in the main KDC code
+ */
+ if (flags & SAMBA_KDC_FLAG_SKIP_PAC_BUFFER) {
+ continue;
+ }
+
+ if (deleg_blob != NULL) {
+ type_blob = *deleg_blob;
+ }
+ break;
+ case PAC_TYPE_CREDENTIAL_INFO:
+ /*
+ * Note that we copy the credential blob,
+ * as it's only usable with the PKINIT based
+ * AS-REP reply key, it's only available on the
+ * host which did the AS-REQ/AS-REP exchange.
+ *
+ * This matches Windows 2008R2...
+ */
+ break;
+ case PAC_TYPE_LOGON_NAME:
+ /*
+ * This is generated in the main KDC code
+ */
+ if (flags & SAMBA_KDC_FLAG_SKIP_PAC_BUFFER) {
+ continue;
+ }
+
+ type_blob = data_blob_const(&zero_byte, 1);
+
+ if (upn_dns_info_idx == -1 && upn_blob != NULL) {
+ /* inject UPN_DNS_INFO behind */
+ forced_next_type = PAC_TYPE_UPN_DNS_INFO;
+ }
+ break;
+ case PAC_TYPE_UPN_DNS_INFO:
+ /*
+ * Replace in the RODC case, otherwise
+ * upn_blob is NULL and we just copy.
+ */
+ if (upn_blob != NULL) {
+ type_blob = *upn_blob;
+ }
+ break;
+ case PAC_TYPE_SRV_CHECKSUM:
+ /*
+ * This is generated in the main KDC code
+ */
+ if (flags & SAMBA_KDC_FLAG_SKIP_PAC_BUFFER) {
+ continue;
+ }
+
+ type_blob = data_blob_const(&zero_byte, 1);
+
+ if (requester_sid_idx == -1 && requester_sid_blob != NULL) {
+ /* inject REQUESTER_SID behind */
+ forced_next_type = PAC_TYPE_REQUESTER_SID;
+ }
+ break;
+ case PAC_TYPE_KDC_CHECKSUM:
+ /*
+ * This is generated in the main KDC code
+ */
+ if (flags & SAMBA_KDC_FLAG_SKIP_PAC_BUFFER) {
+ continue;
+ }
+
+ type_blob = data_blob_const(&zero_byte, 1);
+
+ break;
+ case PAC_TYPE_TICKET_CHECKSUM:
+ /*
+ * This is generated in the main KDC code
+ */
+ if (flags & SAMBA_KDC_FLAG_SKIP_PAC_BUFFER) {
+ continue;
+ }
+
+ type_blob = data_blob_const(&zero_byte, 1);
+
+ break;
+ case PAC_TYPE_ATTRIBUTES_INFO:
+ if (!is_untrusted && is_tgs) {
+ /* just copy... */
+ break;
+ }
+
+ continue;
+ case PAC_TYPE_REQUESTER_SID:
+ if (!is_tgs) {
+ continue;
+ }
+
+ /*
+ * Replace in the RODC case, otherwise
+ * requester_sid_blob is NULL and we just copy.
+ */
+ if (requester_sid_blob != NULL) {
+ type_blob = *requester_sid_blob;
+ }
+ break;
+ default:
+ /* just copy... */
+ break;
+ }
+
+ if (type_blob.length != 0) {
+ code = smb_krb5_copy_data_contents(&type_data,
+ type_blob.data,
+ type_blob.length);
+ if (code != 0) {
+ goto done;
+ }
+ } else {
+ code = krb5_pac_get_buffer(context,
+ old_pac,
+ type,
+ &type_data);
+ if (code != 0) {
+ goto done;
+ }
+ }
+
+ code = krb5_pac_add_buffer(context,
+ new_pac,
+ type,
+ &type_data);
+ smb_krb5_free_data_contents(context, &type_data);
+ if (code != 0) {
+ goto done;
+ }
+ }
+
+ code = 0;
+done:
+ TALLOC_FREE(pac_blob);
+ TALLOC_FREE(upn_blob);
+ TALLOC_FREE(deleg_blob);
+ SAFE_FREE(types);
+ return code;
+}