/*****************************************************************************\
 *  cred_common.c
 *****************************************************************************
 *  Copyright (C) SchedMD LLC.
 *
 *  This file is part of Slurm, a resource management program.
 *  For details, see <https://slurm.schedmd.com/>.
 *  Please also read the included file: DISCLAIMER.
 *
 *  Slurm is free software; you can redistribute it and/or modify it under
 *  the terms of the GNU General Public License as published by the Free
 *  Software Foundation; either version 2 of the License, or (at your option)
 *  any later version.
 *
 *  In addition, as a special exception, the copyright holders give permission
 *  to link the code of portions of this program with the OpenSSL library under
 *  certain conditions as described in each individual source file, and
 *  distribute linked combinations including the two. You must obey the GNU
 *  General Public License in all respects for all of the code used other than
 *  OpenSSL. If you modify file(s) with this exception, you may extend this
 *  exception to your version of the file(s), but you are not obligated to do
 *  so. If you do not wish to do so, delete this exception statement from your
 *  version.  If you delete this exception statement from all source files in
 *  the program, then also delete it here.
 *
 *  Slurm is distributed in the hope that it will be useful, but WITHOUT ANY
 *  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 *  FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
 *  details.
 *
 *  You should have received a copy of the GNU General Public License along
 *  with Slurm; if not, write to the Free Software Foundation, Inc.,
 *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA.
\*****************************************************************************/

#include "slurm/slurm_errno.h"

#include "src/common/bitstring.h"
#include "src/common/group_cache.h"
#include "src/common/identity.h"
#include "src/common/log.h"
#include "src/common/slurm_protocol_pack.h"
#include "src/common/xmalloc.h"
#include "src/common/xstring.h"

#include "src/interfaces/cred.h"
#include "src/interfaces/gres.h"
#include "src/interfaces/switch.h"

/*
 * This is a way to avoid breaking RPC compatibility, while allowing an
 * additional field to be sent. This works by hiding the extra field within
 * an existing string field behind a trailing NUL.
 * Two caveats:
 * - If a NULL pointer was previously sent, it would now be received as instead.
 * - If the receiving side verifies the string length it will see the full
 *   payload length, which is no longer equal to the strlen().
 */
#define safe_unpackstr_and_switch(valp, switch, buf, proto) 	\
do {								\
	xassert(buf->magic == BUF_MAGIC);			\
	if (_unpackstr_and_switch(valp, switch, buf, proto))	\
		goto unpack_error;				\
} while (0)

static void _packstr_and_switch(char *string, void *switch_step, buf_t *buffer,
				uint16_t protocol_version)
{
	uint32_t start = 0, end = 0;

	if (!switch_step) {
		packstr(string, buffer);
		return;
	}

	if (!string)
		string = "";

	start = get_buf_offset(buffer);
	packstr(string, buffer);
	switch_g_stepinfo_pack(switch_step, buffer, protocol_version);
	pack8(0, buffer); /* ensure trailing NUL */
	end = get_buf_offset(buffer);
	set_buf_offset(buffer, start);
	pack32(end - start - 4, buffer);
	set_buf_offset(buffer, end);
}

static int _unpackstr_and_switch(char **string, void **switch_step,
				 buf_t *buffer, uint16_t protocol_version)
{
	uint32_t start = 0;
	uint32_t len = 0, string_len = 0;

	start = get_buf_offset(buffer);
	safe_unpackstr_xmalloc(string, &len, buffer);

	if (!*string)
		return SLURM_SUCCESS;

	/* Ignore this in client commands and slurmd. Only stepd needs it. */
	if (!running_in_slurmstepd())
		return SLURM_SUCCESS;

	string_len = strlen(*string) + 1;

	if (len > string_len) {
		dynamic_plugin_data_t *switch_tmp = NULL;
		/* Second hidden field */
		uint32_t end = get_buf_offset(buffer);
		set_buf_offset(buffer, start + string_len + 4);
		if (switch_g_stepinfo_unpack(&switch_tmp, buffer,
					     protocol_version)) {
			error("switch_g_stepinfo_unpack: %m");
			switch_g_stepinfo_free(switch_tmp);
			goto unpack_error;
		}
		*switch_step = switch_tmp;
		set_buf_offset(buffer, end);
	}

	return SLURM_SUCCESS;

unpack_error:
	return SLURM_ERROR;
}

extern slurm_cred_t *cred_create(slurm_cred_arg_t *cred,
				 uint16_t protocol_version)
{
	slurm_cred_t *credential;
	uint32_t tot_core_cnt = 0;
	buf_t *buffer;

	time_t ctime = time(NULL);

	credential = slurm_cred_alloc(false);
	credential->buffer = buffer = init_buf(4096);
	credential->buf_version = protocol_version;

	if (protocol_version >= SLURM_24_11_PROTOCOL_VERSION) {
		pack_step_id(&cred->step_id, buffer, protocol_version);
		pack_identity(cred->id, buffer, protocol_version);

		(void) gres_job_state_pack(cred->job_gres_list, buffer,
					   cred->step_id.job_id, false,
					   protocol_version);
		gres_step_state_pack(cred->step_gres_list, buffer,
				     &cred->step_id, protocol_version);
		pack16(cred->job_core_spec, buffer);
		packstr(cred->job_account, buffer);
		slurm_pack_addr_array(
			cred->job_node_addrs,
			cred->job_node_addrs ? cred->job_nhosts : 0,
			buffer);
		packstr(cred->job_alias_list, buffer);
		packstr(cred->job_comment, buffer);
		packstr(cred->job_constraints, buffer);
		pack_time(cred->job_end_time, buffer);
		packstr(cred->job_extra, buffer);
		pack16(cred->job_oversubscribe, buffer);
		packstr(cred->job_partition, buffer);
		packstr(cred->job_reservation, buffer);
		pack16(cred->job_restart_cnt, buffer);
		pack_time(cred->job_start_time, buffer);
		packstr(cred->job_std_err, buffer);
		packstr(cred->job_std_in, buffer);
		packstr(cred->job_std_out, buffer);
		packstr(cred->step_hostlist, buffer);
		pack16(cred->job_x11, buffer);
		pack_time(ctime, buffer);

		if (cred->job_core_bitmap)
			tot_core_cnt = bit_size(cred->job_core_bitmap);
		pack32(tot_core_cnt, buffer);
		pack_bit_str_hex(cred->job_core_bitmap, buffer);
		pack_bit_str_hex(cred->step_core_bitmap, buffer);
		pack16(cred->core_array_size, buffer);
		if (cred->core_array_size) {
			pack16_array(cred->cores_per_socket,
				     cred->core_array_size,
				     buffer);
			pack16_array(cred->sockets_per_node,
				     cred->core_array_size,
				     buffer);
			pack32_array(cred->sock_core_rep_count,
				     cred->core_array_size,
				     buffer);
		}
		pack32(cred->cpu_array_count, buffer);
		if (cred->cpu_array_count) {
			pack16_array(cred->cpu_array,
				     cred->cpu_array_count,
				     buffer);
			pack32_array(cred->cpu_array_reps,
				     cred->cpu_array_count,
				     buffer);
		}
		pack32(cred->job_nhosts, buffer);
		pack32(cred->job_ntasks, buffer);
		packstr(cred->job_hostlist, buffer);
		packstr(cred->job_licenses, buffer);
		pack32(cred->job_mem_alloc_size, buffer);
		if (cred->job_mem_alloc_size) {
			pack64_array(cred->job_mem_alloc,
				     cred->job_mem_alloc_size,
				     buffer);
			pack32_array(cred->job_mem_alloc_rep_count,
				     cred->job_mem_alloc_size,
				     buffer);
		}
		pack32(cred->step_mem_alloc_size, buffer);
		if (cred->step_mem_alloc_size) {
			pack64_array(cred->step_mem_alloc,
				     cred->step_mem_alloc_size,
				     buffer);
			pack32_array(cred->step_mem_alloc_rep_count,
				     cred->step_mem_alloc_size,
				     buffer);
		}
		packstr(cred->job_selinux_context, buffer);
		switch_g_stepinfo_pack(cred->switch_step, buffer,
				       protocol_version);
	} else if (protocol_version >= SLURM_MIN_PROTOCOL_VERSION) {
		pack_step_id(&cred->step_id, buffer, protocol_version);
		pack_identity(cred->id, buffer, protocol_version);

		(void) gres_job_state_pack(cred->job_gres_list, buffer,
					   cred->step_id.job_id, false,
					   protocol_version);
		gres_step_state_pack(cred->step_gres_list, buffer,
				     &cred->step_id, protocol_version);
		pack16(cred->job_core_spec, buffer);
		packstr(cred->job_account, buffer);
		slurm_pack_addr_array(
			cred->job_node_addrs,
			cred->job_node_addrs ? cred->job_nhosts : 0,
			buffer);
		packstr(cred->job_alias_list, buffer);
		packstr(cred->job_comment, buffer);
		packstr(cred->job_constraints, buffer);
		pack_time(cred->job_end_time, buffer);
		packstr(cred->job_extra, buffer);
		pack16(cred->job_oversubscribe, buffer);
		_packstr_and_switch(cred->job_partition, cred->switch_step,
				    buffer, protocol_version);
		packstr(cred->job_reservation, buffer);
		pack16(cred->job_restart_cnt, buffer);
		pack_time(cred->job_start_time, buffer);
		packstr(cred->job_std_err, buffer);
		packstr(cred->job_std_in, buffer);
		packstr(cred->job_std_out, buffer);
		packstr(cred->step_hostlist, buffer);
		pack16(cred->job_x11, buffer);
		pack_time(ctime, buffer);

		if (cred->job_core_bitmap)
			tot_core_cnt = bit_size(cred->job_core_bitmap);
		pack32(tot_core_cnt, buffer);
		pack_bit_str_hex(cred->job_core_bitmap, buffer);
		pack_bit_str_hex(cred->step_core_bitmap, buffer);
		pack16(cred->core_array_size, buffer);
		if (cred->core_array_size) {
			pack16_array(cred->cores_per_socket,
				     cred->core_array_size,
				     buffer);
			pack16_array(cred->sockets_per_node,
				     cred->core_array_size,
				     buffer);
			pack32_array(cred->sock_core_rep_count,
				     cred->core_array_size,
				     buffer);
		}
		pack32(cred->cpu_array_count, buffer);
		if (cred->cpu_array_count) {
			pack16_array(cred->cpu_array,
				     cred->cpu_array_count,
				     buffer);
			pack32_array(cred->cpu_array_reps,
				     cred->cpu_array_count,
				     buffer);
		}
		pack32(cred->job_nhosts, buffer);
		pack32(cred->job_ntasks, buffer);
		packstr(cred->job_hostlist, buffer);
		packstr(cred->job_licenses, buffer);
		pack32(cred->job_mem_alloc_size, buffer);
		if (cred->job_mem_alloc_size) {
			pack64_array(cred->job_mem_alloc,
				     cred->job_mem_alloc_size,
				     buffer);
			pack32_array(cred->job_mem_alloc_rep_count,
				     cred->job_mem_alloc_size,
				     buffer);
		}
		pack32(cred->step_mem_alloc_size, buffer);
		if (cred->step_mem_alloc_size) {
			pack64_array(cred->step_mem_alloc,
				     cred->step_mem_alloc_size,
				     buffer);
			pack32_array(cred->step_mem_alloc_rep_count,
				     cred->step_mem_alloc_size,
				     buffer);
		}
		packstr(cred->job_selinux_context, buffer);
	}

	return credential;
}

extern int cred_unpack(void **out, buf_t *buffer, uint16_t protocol_version)
{
	uint32_t len, uint32_tmp;
	slurm_cred_t *cred = NULL;
	slurm_cred_arg_t *cred_arg = NULL;
	char *bit_fmt_str = NULL;
	dynamic_plugin_data_t *switch_tmp = NULL;
	uint32_t tot_core_cnt;

	cred = slurm_cred_alloc(true);
	cred_arg = cred->arg;
	if (protocol_version >= SLURM_24_11_PROTOCOL_VERSION) {
		if (unpack_step_id_members(&cred_arg->step_id, buffer,
					   protocol_version) != SLURM_SUCCESS)
			goto unpack_error;

		if (unpack_identity(&cred_arg->id, buffer, protocol_version))
			goto unpack_error;

		if (gres_job_state_unpack(&cred_arg->job_gres_list, buffer,
					  cred_arg->step_id.job_id,
					  protocol_version)
		    != SLURM_SUCCESS)
			goto unpack_error;
		if (gres_step_state_unpack(&cred_arg->step_gres_list,
					   buffer, &cred_arg->step_id,
					   protocol_version)
		    != SLURM_SUCCESS) {
			goto unpack_error;
		}
		safe_unpack16(&cred_arg->job_core_spec, buffer);
		safe_unpackstr(&cred_arg->job_account, buffer);
		if (slurm_unpack_addr_array(&cred_arg->job_node_addrs,
					    &uint32_tmp, buffer))
			goto unpack_error;
		safe_unpackstr(&cred_arg->job_alias_list, buffer);
		safe_unpackstr(&cred_arg->job_comment, buffer);
		safe_unpackstr(&cred_arg->job_constraints, buffer);
		safe_unpack_time(&cred_arg->job_end_time, buffer);
		safe_unpackstr(&cred_arg->job_extra, buffer);
		safe_unpack16(&cred_arg->job_oversubscribe, buffer);
		safe_unpackstr(&cred_arg->job_partition, buffer);
		safe_unpackstr(&cred_arg->job_reservation, buffer);
		safe_unpack16(&cred_arg->job_restart_cnt, buffer);
		safe_unpack_time(&cred_arg->job_start_time, buffer);
		safe_unpackstr(&cred_arg->job_std_err, buffer);
		safe_unpackstr(&cred_arg->job_std_in, buffer);
		safe_unpackstr(&cred_arg->job_std_out, buffer);
		safe_unpackstr(&cred_arg->step_hostlist, buffer);
		safe_unpack16(&cred_arg->job_x11, buffer);
		safe_unpack_time(&cred->ctime, buffer);
		safe_unpack32(&tot_core_cnt, buffer);
		unpack_bit_str_hex(&cred_arg->job_core_bitmap, buffer);
		unpack_bit_str_hex(&cred_arg->step_core_bitmap, buffer);
		safe_unpack16(&cred_arg->core_array_size, buffer);
		if (cred_arg->core_array_size) {
			safe_unpack16_array(&cred_arg->cores_per_socket, &len,
					    buffer);
			if (len != cred_arg->core_array_size)
				goto unpack_error;
			safe_unpack16_array(&cred_arg->sockets_per_node, &len,
					    buffer);
			if (len != cred_arg->core_array_size)
				goto unpack_error;
			safe_unpack32_array(&cred_arg->sock_core_rep_count,
					    &len, buffer);
			if (len != cred_arg->core_array_size)
				goto unpack_error;
		}
		safe_unpack32(&cred_arg->cpu_array_count, buffer);
		if (cred_arg->cpu_array_count) {
			safe_unpack16_array(&cred_arg->cpu_array, &len, buffer);
			if (len != cred_arg->cpu_array_count)
				goto unpack_error;
			safe_unpack32_array(&cred_arg->cpu_array_reps, &len,
					    buffer);
			if (len != cred_arg->cpu_array_count)
				goto unpack_error;
		}
		safe_unpack32(&cred_arg->job_nhosts, buffer);
		safe_unpack32(&cred_arg->job_ntasks, buffer);
		safe_unpackstr(&cred_arg->job_hostlist, buffer);
		safe_unpackstr(&cred_arg->job_licenses, buffer);

		safe_unpack32(&cred_arg->job_mem_alloc_size, buffer);
		if (cred_arg->job_mem_alloc_size) {
			safe_unpack64_array(&cred_arg->job_mem_alloc, &len,
					    buffer);
			if (len != cred_arg->job_mem_alloc_size)
				goto unpack_error;

			safe_unpack32_array(&cred_arg->job_mem_alloc_rep_count,
					    &len, buffer);
			if (len != cred_arg->job_mem_alloc_size)
				goto unpack_error;

		}

		safe_unpack32(&cred_arg->step_mem_alloc_size, buffer);
		if (cred_arg->step_mem_alloc_size) {
			safe_unpack64_array(&cred_arg->step_mem_alloc, &len,
					    buffer);
			if (len != cred_arg->step_mem_alloc_size)
				goto unpack_error;

			safe_unpack32_array(&cred_arg->step_mem_alloc_rep_count,
					    &len, buffer);
			if (len != cred_arg->step_mem_alloc_size)
				goto unpack_error;
		}

		safe_unpackstr(&cred_arg->job_selinux_context, buffer);
		if (switch_g_stepinfo_unpack(&switch_tmp, buffer,
					     protocol_version)) {
			switch_g_stepinfo_free(switch_tmp);
			goto unpack_error;
		}
		cred_arg->switch_step = switch_tmp;
	} else if (protocol_version >= SLURM_MIN_PROTOCOL_VERSION) {
		if (unpack_step_id_members(&cred_arg->step_id, buffer,
					   protocol_version) != SLURM_SUCCESS)
			goto unpack_error;

		if (unpack_identity(&cred_arg->id, buffer, protocol_version))
			goto unpack_error;

		if (gres_job_state_unpack(&cred_arg->job_gres_list, buffer,
					  cred_arg->step_id.job_id,
					  protocol_version)
		    != SLURM_SUCCESS)
			goto unpack_error;
		if (gres_step_state_unpack(&cred_arg->step_gres_list,
					   buffer, &cred_arg->step_id,
					   protocol_version)
		    != SLURM_SUCCESS) {
			goto unpack_error;
		}
		safe_unpack16(&cred_arg->job_core_spec, buffer);
		safe_unpackstr(&cred_arg->job_account, buffer);
		if (slurm_unpack_addr_array(&cred_arg->job_node_addrs,
					    &uint32_tmp, buffer))
			goto unpack_error;
		safe_unpackstr(&cred_arg->job_alias_list, buffer);
		safe_unpackstr(&cred_arg->job_comment, buffer);
		safe_unpackstr(&cred_arg->job_constraints, buffer);
		safe_unpack_time(&cred_arg->job_end_time, buffer);
		safe_unpackstr(&cred_arg->job_extra, buffer);
		safe_unpack16(&cred_arg->job_oversubscribe, buffer);
		safe_unpackstr_and_switch(&cred_arg->job_partition,
					  &cred_arg->switch_step, buffer,
					  protocol_version);
		safe_unpackstr(&cred_arg->job_reservation, buffer);
		safe_unpack16(&cred_arg->job_restart_cnt, buffer);
		safe_unpack_time(&cred_arg->job_start_time, buffer);
		safe_unpackstr(&cred_arg->job_std_err, buffer);
		safe_unpackstr(&cred_arg->job_std_in, buffer);
		safe_unpackstr(&cred_arg->job_std_out, buffer);
		safe_unpackstr(&cred_arg->step_hostlist, buffer);
		safe_unpack16(&cred_arg->job_x11, buffer);
		safe_unpack_time(&cred->ctime, buffer);
		safe_unpack32(&tot_core_cnt, buffer);
		unpack_bit_str_hex(&cred_arg->job_core_bitmap, buffer);
		unpack_bit_str_hex(&cred_arg->step_core_bitmap, buffer);
		safe_unpack16(&cred_arg->core_array_size, buffer);
		if (cred_arg->core_array_size) {
			safe_unpack16_array(&cred_arg->cores_per_socket, &len,
					    buffer);
			if (len != cred_arg->core_array_size)
				goto unpack_error;
			safe_unpack16_array(&cred_arg->sockets_per_node, &len,
					    buffer);
			if (len != cred_arg->core_array_size)
				goto unpack_error;
			safe_unpack32_array(&cred_arg->sock_core_rep_count,
					    &len, buffer);
			if (len != cred_arg->core_array_size)
				goto unpack_error;
		}
		safe_unpack32(&cred_arg->cpu_array_count, buffer);
		if (cred_arg->cpu_array_count) {
			safe_unpack16_array(&cred_arg->cpu_array, &len, buffer);
			if (len != cred_arg->cpu_array_count)
				goto unpack_error;
			safe_unpack32_array(&cred_arg->cpu_array_reps, &len,
					    buffer);
			if (len != cred_arg->cpu_array_count)
				goto unpack_error;
		}
		safe_unpack32(&cred_arg->job_nhosts, buffer);
		safe_unpack32(&cred_arg->job_ntasks, buffer);
		safe_unpackstr(&cred_arg->job_hostlist, buffer);
		safe_unpackstr(&cred_arg->job_licenses, buffer);

		safe_unpack32(&cred_arg->job_mem_alloc_size, buffer);
		if (cred_arg->job_mem_alloc_size) {
			safe_unpack64_array(&cred_arg->job_mem_alloc, &len,
					    buffer);
			if (len != cred_arg->job_mem_alloc_size)
				goto unpack_error;

			safe_unpack32_array(&cred_arg->job_mem_alloc_rep_count,
					    &len, buffer);
			if (len != cred_arg->job_mem_alloc_size)
				goto unpack_error;

		}

		safe_unpack32(&cred_arg->step_mem_alloc_size, buffer);
		if (cred_arg->step_mem_alloc_size) {
			safe_unpack64_array(&cred_arg->step_mem_alloc, &len,
					    buffer);
			if (len != cred_arg->step_mem_alloc_size)
				goto unpack_error;

			safe_unpack32_array(&cred_arg->step_mem_alloc_rep_count,
					    &len, buffer);
			if (len != cred_arg->step_mem_alloc_size)
				goto unpack_error;
		}

		safe_unpackstr(&cred_arg->job_selinux_context, buffer);
	} else {
		error("%s: protocol_version %hu not supported",
		      __func__, protocol_version);
		goto unpack_error;
	}

	cred_arg->uid = cred_arg->id->uid;
	cred_arg->gid = cred_arg->id->gid;

	*out = cred;
	return SLURM_SUCCESS;

unpack_error:
	xfree(bit_fmt_str);
	slurm_cred_destroy(cred);
	return SLURM_ERROR;
}

extern slurm_cred_t *cred_unpack_with_signature(buf_t *buffer,
						uint16_t protocol_version)
{
	slurm_cred_t *credential = NULL;
	uint32_t cred_start, cred_len;

	cred_start = get_buf_offset(buffer);

	if ((cred_unpack((void **) &credential, buffer, protocol_version)))
		goto unpack_error;

	credential->sig_offset = get_buf_offset(buffer) - cred_start;

	/* signature follows the main buffer */
	safe_unpackstr(&credential->signature, buffer);

	/*
	 * Both srun and slurmd will unpack the credential just to pack it
	 * again. Hold onto a buffer with the pre-packed representation.
	 */
	if (!running_in_slurmstepd()) {
		cred_len = get_buf_offset(buffer) - cred_start;
		credential->buffer = init_buf(cred_len);
		credential->buf_version = protocol_version;
		memcpy(credential->buffer->head,
		       get_buf_data(buffer) + cred_start,
		       cred_len);
		credential->buffer->processed = cred_len;
	}

	return credential;

unpack_error:
	slurm_cred_destroy(credential);
	return NULL;
}

extern buf_t *sbcast_cred_pack(sbcast_cred_arg_t *sbcast_cred,
			       uint16_t protocol_version)
{
	buf_t *buffer = init_buf(4096);
	time_t now = time(NULL);

	if (protocol_version >= SLURM_25_11_PROTOCOL_VERSION) {
		pack_step_id(&sbcast_cred->step_id, buffer, protocol_version);
		pack_identity(sbcast_cred->id, buffer, protocol_version);
		pack_time(now, buffer);
		pack_time(sbcast_cred->expiration, buffer);
		pack32(sbcast_cred->het_job_id, buffer);
		packstr(sbcast_cred->nodes, buffer);
	} else if (protocol_version >= SLURM_MIN_PROTOCOL_VERSION) {
		pack_identity(sbcast_cred->id, buffer, protocol_version);
		pack_time(now, buffer);
		pack_time(sbcast_cred->expiration, buffer);
		pack32(sbcast_cred->step_id.job_id, buffer);
		pack32(sbcast_cred->het_job_id, buffer);
		pack32(sbcast_cred->step_id.step_id, buffer);
		packstr(sbcast_cred->nodes, buffer);
	}

	return buffer;
}

extern sbcast_cred_t *sbcast_cred_unpack(buf_t *buffer, uint32_t *siglen,
					 uint16_t protocol_version)
{
	sbcast_cred_t *sbcast_cred = xmalloc(sizeof(*sbcast_cred));
	uint32_t cred_start = get_buf_offset(buffer);

	if (protocol_version >= SLURM_25_11_PROTOCOL_VERSION) {
		if (unpack_step_id_members(&sbcast_cred->arg.step_id, buffer,
					   protocol_version))
			goto unpack_error;
		if (unpack_identity(&sbcast_cred->arg.id, buffer,
				    protocol_version))
			goto unpack_error;
		safe_unpack_time(&sbcast_cred->ctime, buffer);
		safe_unpack_time(&sbcast_cred->arg.expiration, buffer);
		safe_unpack32(&sbcast_cred->arg.het_job_id, buffer);
		safe_unpackstr(&sbcast_cred->arg.nodes, buffer);

		if (!sbcast_cred->arg.id->pw_name) {
			uid_t uid = sbcast_cred->arg.id->uid;
			gid_t gid = sbcast_cred->arg.id->gid;

			debug2("%s: need to fetch identity", __func__);
			FREE_NULL_IDENTITY(sbcast_cred->arg.id);
			sbcast_cred->arg.id = fetch_identity(uid, gid, false);
			if (!sbcast_cred->arg.id)
				goto unpack_error;
		}
	} else if (protocol_version >= SLURM_MIN_PROTOCOL_VERSION) {
		if (unpack_identity(&sbcast_cred->arg.id, buffer,
				    protocol_version))
			goto unpack_error;
		safe_unpack_time(&sbcast_cred->ctime, buffer);
		safe_unpack_time(&sbcast_cred->arg.expiration, buffer);
		safe_unpack32(&sbcast_cred->arg.step_id.job_id, buffer);
		safe_unpack32(&sbcast_cred->arg.het_job_id, buffer);
		safe_unpack32(&sbcast_cred->arg.step_id.step_id, buffer);
		safe_unpackstr(&sbcast_cred->arg.nodes, buffer);

		if (!sbcast_cred->arg.id->pw_name) {
			uid_t uid = sbcast_cred->arg.id->uid;
			gid_t gid = sbcast_cred->arg.id->gid;

			debug2("%s: need to fetch identity", __func__);
			FREE_NULL_IDENTITY(sbcast_cred->arg.id);
			sbcast_cred->arg.id = fetch_identity(uid, gid, false);
			if (!sbcast_cred->arg.id)
				goto unpack_error;
		}
	} else
		goto unpack_error;

	identity_debug2(sbcast_cred->arg.id, __func__);

	*siglen = get_buf_offset(buffer) - cred_start;

	/* "signature" must be last */
	safe_unpackstr(&sbcast_cred->signature, buffer);
	if (!sbcast_cred->signature)
		goto unpack_error;

	/*
	 * Preserve a copy of the buffer in srun/sbcast to avoid needing to
	 * repack it later.
	 */
	if (!running_in_slurmd()) {
		uint32_t cred_len = get_buf_offset(buffer) - cred_start;
		sbcast_cred->buffer = init_buf(cred_len);
		memcpy(sbcast_cred->buffer->head,
		       get_buf_data(buffer) + cred_start,
		       cred_len);
		sbcast_cred->buffer->processed = cred_len;
	}

	return sbcast_cred;

unpack_error:
	delete_sbcast_cred(sbcast_cred);
	return NULL;
}
