struct tevent_req *smb2cli_req_create(TALLOC_CTX *mem_ctx,
				      struct tevent_context *ev,
				      struct smbXcli_conn *conn,
				      uint16_t cmd,
				      uint32_t additional_flags,
				      uint32_t clear_flags,
				      uint32_t timeout_msec,
				      struct smbXcli_tcon *tcon,
				      struct smbXcli_session *session,
				      const uint8_t *fixed,
				      uint16_t fixed_len,
				      const uint8_t *dyn,
				      uint32_t dyn_len,
				      uint32_t max_dyn_len)
{
	struct tevent_req *req;
	struct smbXcli_req_state *state;
	uint32_t flags = 0;
	uint32_t tid = 0;
	uint64_t uid = 0;
	bool use_channel_sequence = false;
	uint16_t channel_sequence = 0;
	bool use_replay_flag = false;

	req = tevent_req_create(mem_ctx, &state,
				struct smbXcli_req_state);
	if (req == NULL) {
		return NULL;
	}

	state->ev = ev;
	state->conn = conn;
	state->session = session;
	state->tcon = tcon;

	if (conn->smb2.server.capabilities & SMB2_CAP_PERSISTENT_HANDLES) {
		use_channel_sequence = true;
	} else if (conn->smb2.server.capabilities & SMB2_CAP_MULTI_CHANNEL) {
		use_channel_sequence = true;
	}

	if (smbXcli_conn_protocol(conn) >= PROTOCOL_SMB3_00) {
		use_replay_flag = true;
	}

	if (smbXcli_conn_protocol(conn) >= PROTOCOL_SMB3_11) {
		flags |= SMB2_PRIORITY_VALUE_TO_MASK(conn->smb2.io_priority);
	}

	if (session) {
		uid = session->smb2->session_id;

		if (use_channel_sequence) {
			channel_sequence = session->smb2->channel_sequence;
		}

		if (use_replay_flag && session->smb2->replay_active) {
			additional_flags |= SMB2_HDR_FLAG_REPLAY_OPERATION;
		}

		state->smb2.should_sign = session->smb2->should_sign;
		state->smb2.should_encrypt = session->smb2->should_encrypt;

		if (cmd == SMB2_OP_SESSSETUP &&
		    session->smb2_channel.signing_key.length == 0 &&
		    session->smb2->signing_key.length != 0)
		{
			/*
			 * a session bind needs to be signed
			 */
			state->smb2.should_sign = true;
		}

		if (cmd == SMB2_OP_SESSSETUP &&
		    session->smb2_channel.signing_key.length == 0) {
			state->smb2.should_encrypt = false;
		}

		if (additional_flags & SMB2_HDR_FLAG_SIGNED) {
			if (session->smb2_channel.signing_key.length == 0) {
				tevent_req_nterror(req, NT_STATUS_NO_USER_SESSION_KEY);
				return req;
			}

			additional_flags &= ~SMB2_HDR_FLAG_SIGNED;
			state->smb2.should_sign = true;
		}
	}

	if (tcon) {
		tid = tcon->smb2.tcon_id;

		if (tcon->smb2.should_sign) {
			state->smb2.should_sign = true;
		}
		if (tcon->smb2.should_encrypt) {
			state->smb2.should_encrypt = true;
		}
	}

	if (state->smb2.should_encrypt) {
		state->smb2.should_sign = false;
	}

	state->smb2.recv_iov = talloc_zero_array(state, struct iovec, 3);
	if (state->smb2.recv_iov == NULL) {
		TALLOC_FREE(req);
		return NULL;
	}

	flags |= additional_flags;
	flags &= ~clear_flags;

	state->smb2.fixed = fixed;
	state->smb2.fixed_len = fixed_len;
	state->smb2.dyn = dyn;
	state->smb2.dyn_len = dyn_len;
	state->smb2.max_dyn_len = max_dyn_len;

	if (state->smb2.should_encrypt) {
		SIVAL(state->smb2.transform, SMB2_TF_PROTOCOL_ID, SMB2_TF_MAGIC);
		SBVAL(state->smb2.transform, SMB2_TF_SESSION_ID, uid);
	}

	SIVAL(state->smb2.hdr, SMB2_HDR_PROTOCOL_ID,	SMB2_MAGIC);
	SSVAL(state->smb2.hdr, SMB2_HDR_LENGTH,		SMB2_HDR_BODY);
	SSVAL(state->smb2.hdr, SMB2_HDR_OPCODE,		cmd);
	SSVAL(state->smb2.hdr, SMB2_HDR_CHANNEL_SEQUENCE, channel_sequence);
	SIVAL(state->smb2.hdr, SMB2_HDR_FLAGS,		flags);
	SIVAL(state->smb2.hdr, SMB2_HDR_PID,		0); /* reserved */
	SIVAL(state->smb2.hdr, SMB2_HDR_TID,		tid);
	SBVAL(state->smb2.hdr, SMB2_HDR_SESSION_ID,	uid);

	switch (cmd) {
	case SMB2_OP_CANCEL:
		state->one_way = true;
		break;
	case SMB2_OP_BREAK:
		/*
		 * If this is a dummy request, it will have
		 * UINT64_MAX as message id.
		 * If we send on break acknowledgement,
		 * this gets overwritten later.
		 */
		SBVAL(state->smb2.hdr, SMB2_HDR_MESSAGE_ID, UINT64_MAX);
		break;
	}

	if (timeout_msec > 0) {
		struct timeval endtime;

		endtime = timeval_current_ofs_msec(timeout_msec);
		if (!tevent_req_set_endtime(req, ev, endtime)) {
			return req;
		}
	}

	return req;
}


uint16_t smb1cli_session_current_id(struct smbXcli_session *session)
{
	return session->smb1.session_id;
}


void smb2cli_conn_set_cc_max_chunks(struct smbXcli_conn *conn,
				    uint32_t max_chunks)
{
	conn->smb2.cc_max_chunks = max_chunks;
}


uint32_t smbXcli_tcon_get_fs_attributes(struct smbXcli_tcon *tcon)
{
	return tcon->fs_attributes;
}


bool smbXcli_session_is_authenticated(struct smbXcli_session *session)
{
	const DATA_BLOB *application_key;

	if (session->conn == NULL) {
		return false;
	}

	/*
	 * If we have an application key we had a session key negotiated
	 * at auth time.
	 */
	if (session->conn->protocol >= PROTOCOL_SMB2_02) {
		application_key = &session->smb2->application_key;
	} else {
		application_key = &session->smb1.application_key;
	}

	if (application_key->length == 0) {
		return false;
	}

	return true;
}


struct tevent_req *smb1cli_req_send(TALLOC_CTX *mem_ctx,
				    struct tevent_context *ev,
				    struct smbXcli_conn *conn,
				    uint8_t smb_command,
				    uint8_t additional_flags,
				    uint8_t clear_flags,
				    uint16_t additional_flags2,
				    uint16_t clear_flags2,
				    uint32_t timeout_msec,
				    uint32_t pid,
				    struct smbXcli_tcon *tcon,
				    struct smbXcli_session *session,
				    uint8_t wct, uint16_t *vwv,
				    uint32_t num_bytes,
				    const uint8_t *bytes)
{
	struct tevent_req *req;
	struct iovec iov;
	NTSTATUS status;

	iov.iov_base = discard_const_p(void, bytes);
	iov.iov_len = num_bytes;

	req = smb1cli_req_create(mem_ctx, ev, conn, smb_command,
				 additional_flags, clear_flags,
				 additional_flags2, clear_flags2,
				 timeout_msec,
				 pid, tcon, session,
				 wct, vwv, 1, &iov);
	if (req == NULL) {
		return NULL;
	}
	if (!tevent_req_is_in_progress(req)) {
		return tevent_req_post(req, ev);
	}
	status = smb1cli_req_chain_submit(&req, 1);
	if (tevent_req_nterror(req, status)) {
		return tevent_req_post(req, ev);
	}
	return req;
}


NTSTATUS smb2cli_req_compound_submit(struct tevent_req **reqs,
				     int num_reqs)
{
	struct smbXcli_req_state *state;
	struct tevent_req *subreq;
	struct iovec *iov;
	int i, num_iov, nbt_len;
	int tf_iov = -1;
	const DATA_BLOB *encryption_key = NULL;
	uint64_t encryption_session_id = 0;
	uint64_t nonce_high = UINT64_MAX;
	uint64_t nonce_low = UINT64_MAX;

	/*
	 * 1 for the nbt length, optional TRANSFORM
	 * per request: HDR, fixed, dyn, padding
	 * -1 because the last one does not need padding
	 */

	iov = talloc_array(reqs[0], struct iovec, 1 + 1 + 4*num_reqs - 1);
	if (iov == NULL) {
		return NT_STATUS_NO_MEMORY;
	}

	num_iov = 1;
	nbt_len = 0;

	/*
	 * the session of the first request that requires encryption
	 * specifies the encryption key.
	 */
	for (i=0; i<num_reqs; i++) {
		if (!tevent_req_is_in_progress(reqs[i])) {
			return NT_STATUS_INTERNAL_ERROR;
		}

		state = tevent_req_data(reqs[i], struct smbXcli_req_state);

		if (!smbXcli_conn_is_connected(state->conn)) {
			return NT_STATUS_CONNECTION_DISCONNECTED;
		}

		if ((state->conn->protocol != PROTOCOL_NONE) &&
		    (state->conn->protocol < PROTOCOL_SMB2_02)) {
			return NT_STATUS_REVISION_MISMATCH;
		}

		if (state->session == NULL) {
			continue;
		}

		if (!state->smb2.should_encrypt) {
			continue;
		}

		encryption_key = &state->session->smb2->encryption_key;
		if (encryption_key->length == 0) {
			return NT_STATUS_INVALID_PARAMETER_MIX;
		}

		encryption_session_id = state->session->smb2->session_id;

		state->session->smb2->nonce_low += 1;
		if (state->session->smb2->nonce_low == 0) {
			state->session->smb2->nonce_high += 1;
			state->session->smb2->nonce_low += 1;
		}

		/*
		 * CCM and GCM algorithms must never have their
		 * nonce wrap, or the security of the whole
		 * communication and the keys is destroyed.
		 * We must drop the connection once we have
		 * transfered too much data.
		 *
		 * NOTE: We assume nonces greater than 8 bytes.
		 */
		if (state->session->smb2->nonce_high >=
		    state->session->smb2->nonce_high_max)
		{
			return NT_STATUS_ENCRYPTION_FAILED;
		}

		nonce_high = state->session->smb2->nonce_high_random;
		nonce_high += state->session->smb2->nonce_high;
		nonce_low = state->session->smb2->nonce_low;

		tf_iov = num_iov;
		iov[num_iov].iov_base = state->smb2.transform;
		iov[num_iov].iov_len  = sizeof(state->smb2.transform);
		num_iov += 1;

		SBVAL(state->smb2.transform, SMB2_TF_PROTOCOL_ID, SMB2_TF_MAGIC);
		SBVAL(state->smb2.transform, SMB2_TF_NONCE,
		      nonce_low);
		SBVAL(state->smb2.transform, SMB2_TF_NONCE+8,
		      nonce_high);
		SBVAL(state->smb2.transform, SMB2_TF_SESSION_ID,
		      encryption_session_id);

		nbt_len += SMB2_TF_HDR_SIZE;
		break;
	}

	for (i=0; i<num_reqs; i++) {
		int hdr_iov;
		size_t reqlen;
		bool ret;
		uint16_t opcode;
		uint64_t avail;
		uint16_t charge;
		uint16_t credits;
		uint64_t mid;
		const DATA_BLOB *signing_key = NULL;

		if (!tevent_req_is_in_progress(reqs[i])) {
			return NT_STATUS_INTERNAL_ERROR;
		}

		state = tevent_req_data(reqs[i], struct smbXcli_req_state);

		if (!smbXcli_conn_is_connected(state->conn)) {
			return NT_STATUS_CONNECTION_DISCONNECTED;
		}

		if ((state->conn->protocol != PROTOCOL_NONE) &&
		    (state->conn->protocol < PROTOCOL_SMB2_02)) {
			return NT_STATUS_REVISION_MISMATCH;
		}

		opcode = SVAL(state->smb2.hdr, SMB2_HDR_OPCODE);
		if (opcode == SMB2_OP_CANCEL) {
			goto skip_credits;
		}

		avail = UINT64_MAX - state->conn->smb2.mid;
		if (avail < 1) {
			return NT_STATUS_CONNECTION_ABORTED;
		}

		if (state->conn->smb2.server.capabilities & SMB2_CAP_LARGE_MTU) {
			uint32_t max_dyn_len = 1;

			max_dyn_len = MAX(max_dyn_len, state->smb2.dyn_len);
			max_dyn_len = MAX(max_dyn_len, state->smb2.max_dyn_len);

			charge = (max_dyn_len - 1)/ 65536 + 1;
		} else {
			charge = 1;
		}

		charge = MAX(state->smb2.credit_charge, charge);

		avail = MIN(avail, state->conn->smb2.cur_credits);
		if (avail < charge) {
			return NT_STATUS_INTERNAL_ERROR;
		}

		credits = 0;
		if (state->conn->smb2.max_credits > state->conn->smb2.cur_credits) {
			credits = state->conn->smb2.max_credits -
				  state->conn->smb2.cur_credits;
		}
		if (state->conn->smb2.max_credits >= state->conn->smb2.cur_credits) {
			credits += 1;
		}

		mid = state->conn->smb2.mid;
		state->conn->smb2.mid += charge;
		state->conn->smb2.cur_credits -= charge;

		if (state->conn->smb2.server.capabilities & SMB2_CAP_LARGE_MTU) {
			SSVAL(state->smb2.hdr, SMB2_HDR_CREDIT_CHARGE, charge);
		}
		SSVAL(state->smb2.hdr, SMB2_HDR_CREDIT, credits);
		SBVAL(state->smb2.hdr, SMB2_HDR_MESSAGE_ID, mid);

		state->smb2.cancel_flags = 0;
		state->smb2.cancel_mid = mid;
		state->smb2.cancel_aid = 0;

skip_credits:
		if (state->session && encryption_key == NULL) {
			/*
			 * We prefer the channel signing key if it is
			 * already there.
			 */
			if (state->smb2.should_sign) {
				signing_key = &state->session->smb2_channel.signing_key;
			}

			/*
			 * If it is a channel binding, we already have the main
			 * signing key and try that one.
			 */
			if (signing_key && signing_key->length == 0) {
				signing_key = &state->session->smb2->signing_key;
			}

			/*
			 * If we do not have any session key yet, we skip the
			 * signing of SMB2_OP_SESSSETUP requests.
			 */
			if (signing_key && signing_key->length == 0) {
				signing_key = NULL;
			}
		}

		hdr_iov = num_iov;
		iov[num_iov].iov_base = state->smb2.hdr;
		iov[num_iov].iov_len  = sizeof(state->smb2.hdr);
		num_iov += 1;

		iov[num_iov].iov_base = discard_const(state->smb2.fixed);
		iov[num_iov].iov_len  = state->smb2.fixed_len;
		num_iov += 1;

		if (state->smb2.dyn != NULL) {
			iov[num_iov].iov_base = discard_const(state->smb2.dyn);
			iov[num_iov].iov_len  = state->smb2.dyn_len;
			num_iov += 1;
		}

		reqlen  = sizeof(state->smb2.hdr);
		reqlen += state->smb2.fixed_len;
		reqlen += state->smb2.dyn_len;

		if (i < num_reqs-1) {
			if ((reqlen % 8) > 0) {
				uint8_t pad = 8 - (reqlen % 8);
				iov[num_iov].iov_base = state->smb2.pad;
				iov[num_iov].iov_len = pad;
				num_iov += 1;
				reqlen += pad;
			}
			SIVAL(state->smb2.hdr, SMB2_HDR_NEXT_COMMAND, reqlen);
		}

		state->smb2.encryption_session_id = encryption_session_id;

		if (signing_key != NULL) {
			NTSTATUS status;

			status = smb2_signing_sign_pdu(*signing_key,
						       state->session->conn->protocol,
						       &iov[hdr_iov], num_iov - hdr_iov);
			if (!NT_STATUS_IS_OK(status)) {
				return status;
			}
		}

		nbt_len += reqlen;

		ret = smbXcli_req_set_pending(reqs[i]);
		if (!ret) {
			return NT_STATUS_NO_MEMORY;
		}
	}

	state = tevent_req_data(reqs[0], struct smbXcli_req_state);
	_smb_setlen_tcp(state->length_hdr, nbt_len);
	iov[0].iov_base = state->length_hdr;
	iov[0].iov_len  = sizeof(state->length_hdr);

	if (encryption_key != NULL) {
		NTSTATUS status;
		size_t buflen = nbt_len - SMB2_TF_HDR_SIZE;
		uint8_t *buf;
		int vi;

		buf = talloc_array(iov, uint8_t, buflen);
		if (buf == NULL) {
			return NT_STATUS_NO_MEMORY;
		}

		/*
		 * We copy the buffers before encrypting them,
		 * this is at least currently needed for the
		 * to keep state->smb2.hdr.
		 *
		 * Also the callers may expect there buffers
		 * to be const.
		 */
		for (vi = tf_iov + 1; vi < num_iov; vi++) {
			struct iovec *v = &iov[vi];
			const uint8_t *o = (const uint8_t *)v->iov_base;

			memcpy(buf, o, v->iov_len);
			v->iov_base = (void *)buf;
			buf += v->iov_len;
		}

		status = smb2_signing_encrypt_pdu(*encryption_key,
					state->conn->smb2.server.cipher,
					&iov[tf_iov], num_iov - tf_iov);
		if (!NT_STATUS_IS_OK(status)) {
			return status;
		}
	}

	if (state->conn->dispatch_incoming == NULL) {
		state->conn->dispatch_incoming = smb2cli_conn_dispatch_incoming;
	}

	subreq = writev_send(state, state->ev, state->conn->outgoing,
			     state->conn->sock_fd, false, iov, num_iov);
	if (subreq == NULL) {
		return NT_STATUS_NO_MEMORY;
	}
	tevent_req_set_callback(subreq, smb2cli_req_writev_done, reqs[0]);
	state->write_req = subreq;

	return NT_STATUS_OK;
}


uint8_t smb2cli_session_security_mode(struct smbXcli_session *session)
{
	struct smbXcli_conn *conn = session->conn;
	uint8_t security_mode = 0;

	if (conn == NULL) {
		return security_mode;
	}

	security_mode = SMB2_NEGOTIATE_SIGNING_ENABLED;
	if (conn->mandatory_signing) {
		security_mode |= SMB2_NEGOTIATE_SIGNING_REQUIRED;
	}
	if (session->smb2->should_sign) {
		security_mode |= SMB2_NEGOTIATE_SIGNING_REQUIRED;
	}

	return security_mode;
}


bool smb1cli_tcon_set_values(struct smbXcli_tcon *tcon,
			     uint16_t tcon_id,
			     uint16_t optional_support,
			     uint32_t maximal_access,
			     uint32_t guest_maximal_access,
			     const char *service,
			     const char *fs_type)
{
	tcon->is_smb1 = true;
	tcon->fs_attributes = 0;
	tcon->smb1.tcon_id = tcon_id;
	tcon->smb1.optional_support = optional_support;
	tcon->smb1.maximal_access = maximal_access;
	tcon->smb1.guest_maximal_access = guest_maximal_access;

	TALLOC_FREE(tcon->smb1.service);
	tcon->smb1.service = talloc_strdup(tcon, service);
	if (service != NULL && tcon->smb1.service == NULL) {
		return false;
	}

	TALLOC_FREE(tcon->smb1.fs_type);
	tcon->smb1.fs_type = talloc_strdup(tcon, fs_type);
	if (fs_type != NULL && tcon->smb1.fs_type == NULL) {
		return false;
	}

	return true;
}


NTSTATUS smb2cli_session_set_channel_key(struct smbXcli_session *session,
					 const DATA_BLOB _channel_key,
					 const struct iovec *recv_iov)
{
	struct smbXcli_conn *conn = session->conn;
	uint8_t channel_key[16];
	NTSTATUS status;
	struct _derivation {
		DATA_BLOB label;
		DATA_BLOB context;
	};
	struct {
		struct _derivation signing;
	} derivation = { };

	if (conn == NULL) {
		return NT_STATUS_INVALID_PARAMETER_MIX;
	}

	if (session->smb2_channel.signing_key.length != 0) {
		return NT_STATUS_INVALID_PARAMETER_MIX;
	}

	if (conn->protocol >= PROTOCOL_SMB3_10) {
		struct _derivation *d;
		DATA_BLOB p;

		p = data_blob_const(session->smb2_channel.preauth_sha512,
				sizeof(session->smb2_channel.preauth_sha512));

		d = &derivation.signing;
		d->label = data_blob_string_const_null("SMBSigningKey");
		d->context = p;
	} else if (conn->protocol >= PROTOCOL_SMB2_24) {
		struct _derivation *d;

		d = &derivation.signing;
		d->label = data_blob_string_const_null("SMB2AESCMAC");
		d->context = data_blob_string_const_null("SmbSign");
	}

	ZERO_STRUCT(channel_key);
	memcpy(channel_key, _channel_key.data,
	       MIN(_channel_key.length, sizeof(channel_key)));

	session->smb2_channel.signing_key = data_blob_talloc(session,
						channel_key,
						sizeof(channel_key));
	if (session->smb2_channel.signing_key.data == NULL) {
		ZERO_STRUCT(channel_key);
		return NT_STATUS_NO_MEMORY;
	}

	if (conn->protocol >= PROTOCOL_SMB2_24) {
		struct _derivation *d = &derivation.signing;

		smb2_key_derivation(channel_key, sizeof(channel_key),
				    d->label.data, d->label.length,
				    d->context.data, d->context.length,
				    session->smb2_channel.signing_key.data);
	}
	ZERO_STRUCT(channel_key);

	status = smb2_signing_check_pdu(session->smb2_channel.signing_key,
					session->conn->protocol,
					recv_iov, 3);
	if (!NT_STATUS_IS_OK(status)) {
		return status;
	}

	return NT_STATUS_OK;
}


bool smb1cli_conn_check_signing(struct smbXcli_conn *conn,
				const uint8_t *buf, uint32_t seqnum)
{
	const uint8_t *hdr = buf + NBT_HDR_SIZE;
	size_t len = smb_len_nbt(buf);

	return smb_signing_check_pdu(conn->smb1.signing, hdr, len, seqnum);
}


static void smbXcli_conn_samba_suicide_cleanup(struct tevent_req *req,
					       enum tevent_req_state req_state)
{
	struct smbXcli_conn_samba_suicide_state *state = tevent_req_data(
		req, struct smbXcli_conn_samba_suicide_state);

	TALLOC_FREE(state->write_req);

	if (state->conn == NULL) {
		return;
	}

	if (state->conn->suicide_req == req) {
		state->conn->suicide_req = NULL;
	}
	state->conn = NULL;
}


void smb2cli_conn_set_io_priority(struct smbXcli_conn *conn,
				  uint8_t io_priority)
{
	conn->smb2.io_priority = io_priority;
}


uint16_t smb2cli_session_get_flags(struct smbXcli_session *session)
{
	return session->smb2->session_flags;
}


unsigned int msix_nr_vectors_allocated(const PCIDevice *dev)
{
    return dev->msix_entries_nr;
}


void msix_load(PCIDevice *dev, QEMUFile *f)
{
    unsigned n = dev->msix_entries_nr;
    unsigned int vector;

    if (!msix_present(dev)) {
        return;
    }

    msix_clear_all_vectors(dev);
    qemu_get_buffer(f, dev->msix_table, n * PCI_MSIX_ENTRY_SIZE);
    qemu_get_buffer(f, dev->msix_pba, (n + 7) / 8);
    msix_update_function_masked(dev);

    for (vector = 0; vector < n; vector++) {
        msix_handle_mask_update(dev, vector, true);
    }
}


static uint64_t msix_table_mmio_read(void *opaque, hwaddr addr,
                                     unsigned size)
{
    PCIDevice *dev = opaque;

    return pci_get_long(dev->msix_table + addr);
}


MSIMessage msix_get_message(PCIDevice *dev, unsigned vector)
{
    uint8_t *table_entry = dev->msix_table + vector * PCI_MSIX_ENTRY_SIZE;
    MSIMessage msg;

    msg.address = pci_get_quad(table_entry + PCI_MSIX_ENTRY_LOWER_ADDR);
    msg.data = pci_get_long(table_entry + PCI_MSIX_ENTRY_DATA);
    return msg;
}


static void put_msix_state(QEMUFile *f, void *pv, size_t size)
{
    msix_save(pv, f);
}


void msix_write_config(PCIDevice *dev, uint32_t addr,
                       uint32_t val, int len)
{
    unsigned enable_pos = dev->msix_cap + MSIX_CONTROL_OFFSET;
    int vector;
    bool was_masked;

    if (!msix_present(dev) || !range_covers_byte(addr, len, enable_pos)) {
        return;
    }

    was_masked = dev->msix_function_masked;
    msix_update_function_masked(dev);

    if (!msix_enabled(dev)) {
        return;
    }

    pci_device_deassert_intx(dev);

    if (dev->msix_function_masked == was_masked) {
        return;
    }

    for (vector = 0; vector < dev->msix_entries_nr; ++vector) {
        msix_handle_mask_update(dev, vector,
                                msix_vector_masked(dev, vector, was_masked));
    }
}


void msix_vector_unuse(PCIDevice *dev, unsigned vector)
{
    if (vector >= dev->msix_entries_nr || !dev->msix_entry_used[vector]) {
        return;
    }
    if (--dev->msix_entry_used[vector]) {
        return;
    }
    msix_clr_pending(dev, vector);
}


static gint conv_noconv(gchar *outbuf, gint outlen, const gchar *inbuf)
{
	strncpy2(outbuf, inbuf, outlen);
	return 0;
}


const gchar *conv_get_locale_charset_str(void)
{
	static const gchar *codeset = NULL;

	if (!codeset)
		codeset = conv_get_charset_str(conv_get_locale_charset());

	return codeset ? codeset : CS_INTERNAL;
}


const gchar *conv_get_charset_str(CharSet charset)
{
	GHashTable *table;

	table = conv_get_charset_to_str_table();
	return g_hash_table_lookup(table, GUINT_TO_POINTER(charset));
}


static gint conv_anytoutf8(gchar *outbuf, gint outlen, const gchar *inbuf)
{
	gint r = -1;
	switch (conv_guess_ja_encoding(inbuf)) {
	case C_ISO_2022_JP:
		r = conv_jistoutf8(outbuf, outlen, inbuf);
		break;
	case C_SHIFT_JIS:
		r = conv_sjistoutf8(outbuf, outlen, inbuf);
		break;
	case C_EUC_JP:
		r = conv_euctoutf8(outbuf, outlen, inbuf);
		break;
	default:
		r = 0;
		strncpy2(outbuf, inbuf, outlen);
		break;
	}

	return r;
}


void conv_encode_header_full(gchar *dest, gint len, const gchar *src,
			gint header_len, gboolean addr_field,
			const gchar *out_encoding_)
{
	const gchar *cur_encoding;
	const gchar *out_encoding;
	gint mimestr_len;
	gchar *mimesep_enc;
	gint left;
	const guchar *srcp = src;
	guchar *destp = dest;
	gboolean use_base64;

	cm_return_if_fail(g_utf8_validate(src, -1, NULL) == TRUE);
	cm_return_if_fail(destp != NULL);

	if (MB_CUR_MAX > 1) {
		use_base64 = TRUE;
		mimesep_enc = "?B?";
	} else {
		use_base64 = FALSE;
		mimesep_enc = "?Q?";
	}

	cur_encoding = CS_INTERNAL;

	if (out_encoding_)
		out_encoding = out_encoding_;
	else
		out_encoding = conv_get_outgoing_charset_str();

	if (!strcmp(out_encoding, CS_US_ASCII))
		out_encoding = CS_ISO_8859_1;

	mimestr_len = strlen(MIMESEP_BEGIN) + strlen(out_encoding) +
		strlen(mimesep_enc) + strlen(MIMESEP_END);

	left = MAX_LINELEN - header_len;

	while (*srcp) {
		LBREAK_IF_REQUIRED(left <= 0, TRUE);

		while (isspace(*srcp)) {
			*destp++ = *srcp++;
			left--;
			LBREAK_IF_REQUIRED(left <= 0, TRUE);
		}

		/* output as it is if the next word is ASCII string */
		if (!is_next_nonascii(srcp)) {
			gint word_len;

			word_len = get_next_word_len(srcp);
			LBREAK_IF_REQUIRED(left < word_len, TRUE);
			while (word_len > 0) {
				LBREAK_IF_REQUIRED(left + (MAX_HARD_LINELEN - MAX_LINELEN) <= 0, TRUE)
				*destp++ = *srcp++;
				left--;
				word_len--;
			}

			continue;
		}

		/* don't include parentheses and quotes in encoded strings */
		if (addr_field && (*srcp == '(' || *srcp == ')' || *srcp == '"')) {
			LBREAK_IF_REQUIRED(left < 2, FALSE);
			*destp++ = *srcp++;
			left--;
		}

		while (1) {
			gint mb_len = 0;
			gint cur_len = 0;
			gchar *part_str;
			gchar *out_str;
			gchar *enc_str;
			const guchar *p = srcp;
			gint out_str_len;
			gint out_enc_str_len;
			gint mime_block_len;
			gboolean cont = FALSE;

			while (*p != '\0') {
				if (isspace(*p) && !is_next_nonascii(p + 1))
					break;
				/* don't include parentheses in encoded
				   strings */
				if (addr_field && (*p == '(' || *p == ')' || *p == '"'))
					break;

				mb_len = g_utf8_skip[*p];

				Xstrndup_a(part_str, srcp, cur_len + mb_len, );
				out_str = conv_codeset_strdup
					(part_str, cur_encoding, out_encoding);
				if (!out_str) {
					if (strict_mode) {
						*dest = '\0';
						return;
					} else {
						g_warning("conv_encode_header(): code conversion failed");
						conv_unreadable_8bit(part_str);
						out_str = g_strdup(part_str);
					}
				}
				out_str_len = strlen(out_str);

				if (use_base64)
					out_enc_str_len = B64LEN(out_str_len);
				else
					out_enc_str_len =
						qp_get_q_encoding_len(out_str);

				g_free(out_str);

				if (mimestr_len + out_enc_str_len <= left) {
					cur_len += mb_len;
					p += mb_len;
				} else if (cur_len == 0) {
					left = 0;
					LBREAK_IF_REQUIRED(1, FALSE);
					continue;
				} else {
					cont = TRUE;
					break;
				}
			}

			if (cur_len > 0) {
				Xstrndup_a(part_str, srcp, cur_len, );
				out_str = conv_codeset_strdup
					(part_str, cur_encoding, out_encoding);
				if (!out_str) {
					g_warning("conv_encode_header(): code conversion failed");
					conv_unreadable_8bit(part_str);
					out_str = g_strdup(part_str);
				}
				out_str_len = strlen(out_str);

				if (use_base64)
					out_enc_str_len = B64LEN(out_str_len);
				else
					out_enc_str_len =
						qp_get_q_encoding_len(out_str);

				if (use_base64)
					enc_str = g_base64_encode(out_str, out_str_len);
				else {
					Xalloca(enc_str, out_enc_str_len + 1, );
					qp_q_encode(enc_str, out_str);
				}

				g_free(out_str);

				/* output MIME-encoded string block */
				mime_block_len = mimestr_len + strlen(enc_str);
				g_snprintf(destp, mime_block_len + 1,
					   MIMESEP_BEGIN "%s%s%s" MIMESEP_END,
					   out_encoding, mimesep_enc, enc_str);

				if (use_base64)
					g_free(enc_str);

				destp += mime_block_len;
				srcp += cur_len;

				left -= mime_block_len;
			}

			LBREAK_IF_REQUIRED(cont, FALSE);

			if (cur_len == 0)
				break;
		}
	}

	*destp = '\0';
}


static void conv_unreadable_8bit(gchar *str)
{
	register guchar *p = str;

	while (*p != '\0') {
		/* convert CR+LF -> LF */
		if (*p == '\r' && *(p + 1) == '\n')
			memmove(p, p + 1, strlen(p));
		else if (!IS_ASCII(*p)) *p = SUBST_CHAR;
		p++;
	}
}


static GHashTable *conv_get_charset_to_str_table(void)
{
	static GHashTable *table;
	gint i;

	if (table)
		return table;

	table = g_hash_table_new(NULL, g_direct_equal);

	for (i = 0; i < sizeof(charsets) / sizeof(charsets[0]); i++) {
		if (g_hash_table_lookup(table, GUINT_TO_POINTER(charsets[i].charset))
		    == NULL) {
			g_hash_table_insert
				(table, GUINT_TO_POINTER(charsets[i].charset),
				 charsets[i].name);
		}
	}

	return table;
}


static void send1extra(int f, struct file_struct *file, struct file_list *flist)
{
	char fbuf[MAXPATHLEN];
	item_list *relname_list;
	int len, dlen, flags = FLAG_DIVERT_DIRS | FLAG_CONTENT_DIR;
	size_t j;

	f_name(file, fbuf);
	dlen = strlen(fbuf);

	if (!change_pathname(file, NULL, 0))
		exit_cleanup(RERR_FILESELECT);

	change_local_filter_dir(fbuf, dlen, send_dir_depth);

	if (file->flags & FLAG_CONTENT_DIR) {
		if (one_file_system) {
			STRUCT_STAT st;
			if (link_stat(fbuf, &st, copy_dirlinks) != 0) {
				interpret_stat_error(fbuf, True);
				return;
			}
			filesystem_dev = st.st_dev;
		}
		send_directory(f, flist, fbuf, dlen, flags);
	}

	if (!relative_paths)
		return;

	memcpy(&relname_list, F_DIR_RELNAMES_P(file), sizeof relname_list);
	if (!relname_list)
		return;

	for (j = 0; j < relname_list->count; j++) {
		char *slash;
		relnamecache *rnp = ((relnamecache**)relname_list->items)[j];
		char name_type = rnp->name_type;

		fbuf[dlen] = '/';
		len = strlcpy(fbuf + dlen + 1, rnp->fname, sizeof fbuf - dlen - 1);
		free(rnp);
		if (len >= (int)sizeof fbuf)
			continue; /* Impossible... */

		slash = strchr(fbuf+dlen+1, '/');
		if (slash) {
			send_implied_dirs(f, flist, fbuf, fbuf+dlen+1, slash, flags, name_type);
			continue;
		}

		if (name_type != NORMAL_NAME) {
			STRUCT_STAT st;
			if (name_type == MISSING_NAME)
				memset(&st, 0, sizeof st);
			else if (link_stat(fbuf, &st, 1) != 0) {
				interpret_stat_error(fbuf, True);
				continue;
			}
			send_file_name(f, flist, fbuf, &st, FLAG_TOP_DIR | flags, ALL_FILTERS);
		} else
			send_file_name(f, flist, fbuf, NULL, FLAG_TOP_DIR | flags, ALL_FILTERS);
	}

	free(relname_list);
}


void clear_file(struct file_struct *file)
{
	/* The +1 zeros out the first char of the basename. */
	memset(file, 0, FILE_STRUCT_LEN + 1);
	/* In an empty entry, F_DEPTH() is an offset to the next non-empty
	 * entry.  Likewise for len32 in the opposite direction.  We assume
	 * that we're alone for now since flist_find() will adjust the counts
	 * it runs into that aren't up-to-date. */
	file->len32 = F_DEPTH(file) = 1;
}


static RETSIGTYPE sigusr1_handler(UNUSED(int val))
{
	exit_cleanup(RERR_SIGNAL1);
}


static void flist_done_allocating(struct file_list *flist)
{
	void *ptr = pool_boundary(flist->file_pool, 8*1024);
	if (flist->pool_boundary == ptr)
		flist->pool_boundary = NULL; /* list didn't use any pool memory */
	else
		flist->pool_boundary = ptr;
}


char *f_name(const struct file_struct *f, char *fbuf)
{
	if (!f || !F_IS_ACTIVE(f))
		return NULL;

	if (!fbuf)
		fbuf = f_name_buf();

	if (f->dirname) {
		int len = strlen(f->dirname);
		memcpy(fbuf, f->dirname, len);
		fbuf[len] = '/';
		strlcpy(fbuf + len + 1, f->basename, MAXPATHLEN - (len + 1));
	} else
		strlcpy(fbuf, f->basename, MAXPATHLEN);

	return fbuf;
}


static int show_filelist_p(void)
{
	return INFO_GTE(FLIST, 1) && xfer_dirs && !am_server && !inc_recurse;
}


static pid_t do_cmd(char *cmd, char *machine, char *user, char **remote_argv, int remote_argc,
		    int *f_in_p, int *f_out_p)
{
	int i, argc = 0;
	char *args[MAX_ARGS], *need_to_free = NULL;
	pid_t pid;
	int dash_l_set = 0;

	if (!read_batch && !local_server) {
		char *t, *f, in_quote = '\0';
		char *rsh_env = getenv(RSYNC_RSH_ENV);
		if (!cmd)
			cmd = rsh_env;
		if (!cmd)
			cmd = RSYNC_RSH;
		cmd = need_to_free = strdup(cmd);
		if (!cmd)
			goto oom;

		for (t = f = cmd; *f; f++) {
			if (*f == ' ')
				continue;
			/* Comparison leaves rooms for server_options(). */
			if (argc >= MAX_ARGS - MAX_SERVER_ARGS)
				goto arg_overflow;
			args[argc++] = t;
			while (*f != ' ' || in_quote) {
				if (!*f) {
					if (in_quote) {
						rprintf(FERROR,
						    "Missing trailing-%c in remote-shell command.\n",
						    in_quote);
						exit_cleanup(RERR_SYNTAX);
					}
					f--;
					break;
				}
				if (*f == '\'' || *f == '"') {
					if (!in_quote) {
						in_quote = *f++;
						continue;
					}
					if (*f == in_quote && *++f != in_quote) {
						in_quote = '\0';
						continue;
					}
				}
				*t++ = *f++;
			}
			*t++ = '\0';
		}

		/* check to see if we've already been given '-l user' in
		 * the remote-shell command */
		for (i = 0; i < argc-1; i++) {
			if (!strcmp(args[i], "-l") && args[i+1][0] != '-')
				dash_l_set = 1;
		}

#ifdef HAVE_REMSH
		/* remsh (on HPUX) takes the arguments the other way around */
		args[argc++] = machine;
		if (user && !(daemon_over_rsh && dash_l_set)) {
			args[argc++] = "-l";
			args[argc++] = user;
		}
#else
		if (user && !(daemon_over_rsh && dash_l_set)) {
			args[argc++] = "-l";
			args[argc++] = user;
		}
		args[argc++] = machine;
#endif

		args[argc++] = rsync_path;

		if (blocking_io < 0) {
			char *cp;
			if ((cp = strrchr(cmd, '/')) != NULL)
				cp++;
			else
				cp = cmd;
			if (strcmp(cp, "rsh") == 0 || strcmp(cp, "remsh") == 0)
				blocking_io = 1;
		}

		server_options(args,&argc);

		if (argc >= MAX_ARGS - 2)
			goto arg_overflow;
	}

	args[argc++] = ".";

	if (!daemon_over_rsh) {
		while (remote_argc > 0) {
			if (argc >= MAX_ARGS - 1) {
			  arg_overflow:
				rprintf(FERROR, "internal: args[] overflowed in do_cmd()\n");
				exit_cleanup(RERR_SYNTAX);
			}
			if (**remote_argv == '-') {
				if (asprintf(args + argc++, "./%s", *remote_argv++) < 0)
					out_of_memory("do_cmd");
			} else
				args[argc++] = *remote_argv++;
			remote_argc--;
		}
	}

	args[argc] = NULL;

	if (DEBUG_GTE(CMD, 2)) {
		for (i = 0; i < argc; i++)
			rprintf(FCLIENT, "cmd[%d]=%s ", i, args[i]);
		rprintf(FCLIENT, "\n");
	}

	if (read_batch) {
		int from_gen_pipe[2];
		set_allow_inc_recurse();
		if (fd_pair(from_gen_pipe) < 0) {
			rsyserr(FERROR, errno, "pipe");
			exit_cleanup(RERR_IPC);
		}
		batch_gen_fd = from_gen_pipe[0];
		*f_out_p = from_gen_pipe[1];
		*f_in_p = batch_fd;
		pid = (pid_t)-1; /* no child pid */
#ifdef ICONV_CONST
		setup_iconv();
#endif
	} else if (local_server) {
		/* If the user didn't request --[no-]whole-file, force
		 * it on, but only if we're not batch processing. */
		if (whole_file < 0 && !write_batch)
			whole_file = 1;
		set_allow_inc_recurse();
		pid = local_child(argc, args, f_in_p, f_out_p, child_main);
#ifdef ICONV_CONST
		setup_iconv();
#endif
	} else {
		pid = piped_child(args, f_in_p, f_out_p);
#ifdef ICONV_CONST
		setup_iconv();
#endif
		if (protect_args && !daemon_over_rsh)
			send_protected_args(*f_out_p, args);
	}

	if (need_to_free)
		free(need_to_free);

	return pid;

  oom:
	out_of_memory("do_cmd");
	return 0; /* not reached */
}


int io_end_multiplex_out(int mode)
{
	int ret = iobuf.out_empty_len ? iobuf.out_fd : -1;

	if (msgs2stderr && DEBUG_GTE(IO, 2))
		rprintf(FINFO, "[%s] io_end_multiplex_out(mode=%d)\n", who_am_i(), mode);

	if (mode != MPLX_TO_BUFFERED)
		io_end_buffering_out(mode);
	else
		io_flush(FULL_FLUSH);

	iobuf.out.len = 0;
	iobuf.out_empty_len = 0;
	if (got_kill_signal > 0) /* Just in case... */
		handle_kill_signal(False);
	got_kill_signal = -1;

	return ret;
}


static void fsort_tmp(struct file_struct **fp, size_t num,
		      struct file_struct **tmp)
{
	struct file_struct **f1, **f2, **t;
	size_t n1, n2;

	n1 = num / 2;
	n2 = num - n1;
	f1 = fp;
	f2 = fp + n1;

	if (n1 > 1)
		fsort_tmp(f1, n1, tmp);
	if (n2 > 1)
		fsort_tmp(f2, n2, tmp);

	while (f_name_cmp(*f1, *f2) <= 0) {
		if (!--n1)
			return;
		f1++;
	}

	t = tmp;
	memcpy(t, f1, n1 * PTR_SIZE);

	*f1++ = *f2++, n2--;

	while (n1 > 0 && n2 > 0) {
		if (f_name_cmp(*t, *f2) <= 0)
			*f1++ = *t++, n1--;
		else
			*f1++ = *f2++, n2--;
	}

	if (n1 > 0)
		memcpy(f1, t, n1 * PTR_SIZE);
}


int f_name_cmp(const struct file_struct *f1, const struct file_struct *f2)
{
	int dif;
	const uchar *c1, *c2;
	enum fnc_state state1, state2;
	enum fnc_type type1, type2;
	enum fnc_type t_path = protocol_version >= 29 ? t_PATH : t_ITEM;

	if (!f1 || !F_IS_ACTIVE(f1)) {
		if (!f2 || !F_IS_ACTIVE(f2))
			return 0;
		return -1;
	}
	if (!f2 || !F_IS_ACTIVE(f2))
		return 1;

	c1 = (uchar*)f1->dirname;
	c2 = (uchar*)f2->dirname;
	if (c1 == c2)
		c1 = c2 = NULL;
	if (!c1) {
		type1 = S_ISDIR(f1->mode) ? t_path : t_ITEM;
		c1 = (const uchar*)f1->basename;
		if (type1 == t_PATH && *c1 == '.' && !c1[1]) {
			type1 = t_ITEM;
			state1 = s_TRAILING;
			c1 = (uchar*)"";
		} else
			state1 = s_BASE;
	} else {
		type1 = t_path;
		state1 = s_DIR;
	}
	if (!c2) {
		type2 = S_ISDIR(f2->mode) ? t_path : t_ITEM;
		c2 = (const uchar*)f2->basename;
		if (type2 == t_PATH && *c2 == '.' && !c2[1]) {
			type2 = t_ITEM;
			state2 = s_TRAILING;
			c2 = (uchar*)"";
		} else
			state2 = s_BASE;
	} else {
		type2 = t_path;
		state2 = s_DIR;
	}

	if (type1 != type2)
		return type1 == t_PATH ? 1 : -1;

	do {
		if (!*c1) {
			switch (state1) {
			case s_DIR:
				state1 = s_SLASH;
				c1 = (uchar*)"/";
				break;
			case s_SLASH:
				type1 = S_ISDIR(f1->mode) ? t_path : t_ITEM;
				c1 = (const uchar*)f1->basename;
				if (type1 == t_PATH && *c1 == '.' && !c1[1]) {
					type1 = t_ITEM;
					state1 = s_TRAILING;
					c1 = (uchar*)"";
				} else
					state1 = s_BASE;
				break;
			case s_BASE:
				state1 = s_TRAILING;
				if (type1 == t_PATH) {
					c1 = (uchar*)"/";
					break;
				}
				/* FALL THROUGH */
			case s_TRAILING:
				type1 = t_ITEM;
				break;
			}
			if (*c2 && type1 != type2)
				return type1 == t_PATH ? 1 : -1;
		}
		if (!*c2) {
			switch (state2) {
			case s_DIR:
				state2 = s_SLASH;
				c2 = (uchar*)"/";
				break;
			case s_SLASH:
				type2 = S_ISDIR(f2->mode) ? t_path : t_ITEM;
				c2 = (const uchar*)f2->basename;
				if (type2 == t_PATH && *c2 == '.' && !c2[1]) {
					type2 = t_ITEM;
					state2 = s_TRAILING;
					c2 = (uchar*)"";
				} else
					state2 = s_BASE;
				break;
			case s_BASE:
				state2 = s_TRAILING;
				if (type2 == t_PATH) {
					c2 = (uchar*)"/";
					break;
				}
				/* FALL THROUGH */
			case s_TRAILING:
				found_prefix = 1;
				if (!*c1)
					return 0;
				type2 = t_ITEM;
				break;
			}
			if (type1 != type2)
				return type1 == t_PATH ? 1 : -1;
		}
	} while ((dif = (int)*c1++ - (int)*c2++) == 0);

	return dif;
}


static RETSIGTYPE rsync_panic_handler(UNUSED(int whatsig))
{
	char cmd_buf[300];
	int ret, pid_int = getpid();

	snprintf(cmd_buf, sizeof cmd_buf, get_panic_action(), pid_int, pid_int);

	/* Unless we failed to execute gdb, we allow the process to
	 * continue.  I'm not sure if that's right. */
	ret = system(cmd_buf);
	if (ret)
		_exit(ret);
}


static void output_flist(struct file_list *flist)
{
	char uidbuf[16], gidbuf[16], depthbuf[16];
	struct file_struct *file;
	const char *root, *dir, *slash, *name, *trail;
	const char *who = who_am_i();
	int i;

	rprintf(FINFO, "[%s] flist start=%d, used=%d, low=%d, high=%d\n",
		who, flist->ndx_start, flist->used, flist->low, flist->high);
	for (i = 0; i < flist->used; i++) {
		file = flist->files[i];
		if ((am_root || am_sender) && uid_ndx) {
			snprintf(uidbuf, sizeof uidbuf, " uid=%u",
				 F_OWNER(file));
		} else
			*uidbuf = '\0';
		if (gid_ndx) {
			static char parens[] = "(\0)\0\0\0";
			char *pp = parens + (file->flags & FLAG_SKIP_GROUP ? 0 : 3);
			snprintf(gidbuf, sizeof gidbuf, " gid=%s%u%s",
				 pp, F_GROUP(file), pp + 2);
		} else
			*gidbuf = '\0';
		if (!am_sender)
			snprintf(depthbuf, sizeof depthbuf, "%d", F_DEPTH(file));
		if (F_IS_ACTIVE(file)) {
			root = am_sender ? NS(F_PATHNAME(file)) : depthbuf;
			if ((dir = file->dirname) == NULL)
				dir = slash = "";
			else
				slash = "/";
			name = file->basename;
			trail = S_ISDIR(file->mode) ? "/" : "";
		} else
			root = dir = slash = name = trail = "";
		rprintf(FINFO,
			"[%s] i=%d %s %s%s%s%s mode=0%o len=%s%s%s flags=%x\n",
			who, i + flist->ndx_start,
			root, dir, slash, name, trail,
			(int)file->mode, comma_num(F_LENGTH(file)),
			uidbuf, gidbuf, file->flags);
	}
}


void write_del_stats(int f)
{
	if (read_batch)
		write_int(f, NDX_DEL_STATS);
	else
		write_ndx(f, NDX_DEL_STATS);
	write_varint(f, stats.deleted_files - stats.deleted_dirs
		      - stats.deleted_symlinks - stats.deleted_devices
		      - stats.deleted_specials);
	write_varint(f, stats.deleted_dirs);
	write_varint(f, stats.deleted_symlinks);
	write_varint(f, stats.deleted_devices);
	write_varint(f, stats.deleted_specials);
}


static int remember_delete(struct file_struct *file, const char *fname, int flags)
{
	int len;

	if (deldelay_cnt == deldelay_size && !flush_delete_delay())
		return 0;

	if (flags & DEL_NO_UID_WRITE)
		deldelay_buf[deldelay_cnt++] = '!';

	while (1) {
		len = snprintf(deldelay_buf + deldelay_cnt,
			       deldelay_size - deldelay_cnt,
			       "%x %s%c",
			       (int)file->mode, fname, '\0');
		if ((deldelay_cnt += len) <= deldelay_size)
			break;
		deldelay_cnt -= len;
		if (!flush_delete_delay())
			return 0;
	}

	return 1;
}


static int try_dests_reg(struct file_struct *file, char *fname, int ndx,
			 char *cmpbuf, stat_x *sxp, int find_exact_for_existing,
			 int itemizing, enum logcode code)
{
	STRUCT_STAT real_st = sxp->st;
	int best_match = -1;
	int match_level = 0;
	int j = 0;

	do {
		pathjoin(cmpbuf, MAXPATHLEN, basis_dir[j], fname);
		if (link_stat(cmpbuf, &sxp->st, 0) < 0 || !S_ISREG(sxp->st.st_mode))
			continue;
		switch (match_level) {
		case 0:
			best_match = j;
			match_level = 1;
			/* FALL THROUGH */
		case 1:
			if (!unchanged_file(cmpbuf, file, &sxp->st))
				continue;
			best_match = j;
			match_level = 2;
			/* FALL THROUGH */
		case 2:
			if (!unchanged_attrs(cmpbuf, file, sxp)) {
				free_stat_x(sxp);
				continue;
			}
			best_match = j;
			match_level = 3;
			break;
		}
		break;
	} while (basis_dir[++j] != NULL);

	if (!match_level)
		return -1;

	if (j != best_match) {
		j = best_match;
		pathjoin(cmpbuf, MAXPATHLEN, basis_dir[j], fname);
		if (link_stat(cmpbuf, &sxp->st, 0) < 0)
			return -1;
	}

	if (match_level == 3 && !copy_dest) {
		if (find_exact_for_existing) {
			if (link_dest && real_st.st_dev == sxp->st.st_dev && real_st.st_ino == sxp->st.st_ino)
				return -1;
			if (do_unlink(fname) < 0 && errno != ENOENT) {
				sxp->st = real_st;
				return -1;
			}
		}
#ifdef SUPPORT_HARD_LINKS
		if (link_dest) {
			if (!hard_link_one(file, fname, cmpbuf, 1))
				goto try_a_copy;
			if (preserve_hard_links && F_IS_HLINKED(file))
				finish_hard_link(file, fname, ndx, &sxp->st, itemizing, code, j);
			if (!maybe_ATTRS_REPORT && (INFO_GTE(NAME, 2) || stdout_format_has_i > 1)) {
				itemize(cmpbuf, file, ndx, 1, sxp,
					ITEM_LOCAL_CHANGE | ITEM_XNAME_FOLLOWS,
					0, "");
			}
		} else
#endif
		{
			if (itemizing)
				itemize(cmpbuf, file, ndx, 0, sxp, 0, 0, NULL);
		}
		if (INFO_GTE(NAME, 2) && maybe_ATTRS_REPORT)
			rprintf(FCLIENT, "%s is uptodate\n", fname);
		return -2;
	}

	if (find_exact_for_existing) {
		sxp->st = real_st;
		return -1;
	}

	if (match_level >= 2) {
#ifdef SUPPORT_HARD_LINKS
	  try_a_copy: /* Copy the file locally. */
#endif
		if (!dry_run && copy_altdest_file(cmpbuf, fname, file) < 0) {
			if (find_exact_for_existing) /* Can get here via hard-link failure */
				sxp->st = real_st;
			return -1;
		}
		if (itemizing)
			itemize(cmpbuf, file, ndx, 0, sxp, ITEM_LOCAL_CHANGE, 0, NULL);
		if (maybe_ATTRS_REPORT
		 && ((!itemizing && INFO_GTE(NAME, 1) && match_level == 2)
		  || (INFO_GTE(NAME, 2) && match_level == 3))) {
			code = match_level == 3 ? FCLIENT : FINFO;
			rprintf(code, "%s%s\n", fname,
				match_level == 3 ? " is uptodate" : "");
		}
#ifdef SUPPORT_HARD_LINKS
		if (preserve_hard_links && F_IS_HLINKED(file))
			finish_hard_link(file, fname, ndx, &sxp->st, itemizing, code, -1);
#endif
		return -2;
	}

	return FNAMECMP_BASIS_DIR_LOW + j;
}


int fd_pair(int fd[2])
{
	int ret;

#ifdef HAVE_SOCKETPAIR
	ret = socketpair(AF_UNIX, SOCK_STREAM, 0, fd);
#else
	ret = pipe(fd);
#endif

	if (ret == 0) {
		set_nonblocking(fd[0]);
		set_nonblocking(fd[1]);
	}

	return ret;
}


static inline void call_glob_match(const char *name, int len, int from_glob,
				   char *arg, int abpos, int fbpos)
{
	char *use_buf;

	ENSURE_MEMSPACE(glob.arg_buf, char, glob.absize, abpos + len + 2);
	memcpy(glob.arg_buf + abpos, name, len);
	abpos += len;
	glob.arg_buf[abpos] = '\0';

	if (fbpos >= 0) {
		ENSURE_MEMSPACE(glob.filt_buf, char, glob.fbsize, fbpos + len + 2);
		memcpy(glob.filt_buf + fbpos, name, len);
		fbpos += len;
		glob.filt_buf[fbpos] = '\0';
		use_buf = glob.filt_buf;
	} else
		use_buf = glob.arg_buf;

	if (from_glob || (arg && len)) {
		STRUCT_STAT st;
		int is_dir;

		if (do_stat(glob.arg_buf, &st) != 0)
			return;
		is_dir = S_ISDIR(st.st_mode) != 0;
		if (arg && !is_dir)
			return;

		if (daemon_filter_list.head
		 && check_filter(&daemon_filter_list, FLOG, use_buf, is_dir) < 0)
			return;
	}

	if (arg) {
		glob.arg_buf[abpos++] = '/';
		glob.arg_buf[abpos] = '\0';
		if (fbpos >= 0) {
			glob.filt_buf[fbpos++] = '/';
			glob.filt_buf[fbpos] = '\0';
		}
		glob_match(arg, abpos, fbpos);
	} else {
		ENSURE_MEMSPACE(glob.argv, char *, glob.maxargs, glob.argc + 1);
		if (!(glob.argv[glob.argc++] = strdup(glob.arg_buf)))
			out_of_memory("glob_match");
	}
}


int bitbag_next_bit(struct bitbag *bb, int after)
{
	uint32 bits, mask;
	int i, ndx = after + 1;
	int slot = ndx / BB_PER_SLOT_BITS;
	ndx %= BB_PER_SLOT_BITS;

	mask = (1u << (ndx % 32)) - 1;
	for (i = ndx / 32; slot < bb->slot_cnt; slot++, i = mask = 0) {
		if (!bb->bits[slot])
			continue;
		for ( ; i < BB_PER_SLOT_INTS; i++, mask = 0) {
			if (!(bits = bb->bits[slot][i] & ~mask))
				continue;
			/* The xor magic figures out the lowest enabled bit in
			 * bits, and the switch quickly computes log2(bit). */
			switch (bits ^ (bits & (bits-1))) {
#define LOG2(n) case 1u << n: return slot*BB_PER_SLOT_BITS + i*32 + n
			    LOG2(0);  LOG2(1);  LOG2(2);  LOG2(3);
			    LOG2(4);  LOG2(5);  LOG2(6);  LOG2(7);
			    LOG2(8);  LOG2(9);  LOG2(10); LOG2(11);
			    LOG2(12); LOG2(13); LOG2(14); LOG2(15);
			    LOG2(16); LOG2(17); LOG2(18); LOG2(19);
			    LOG2(20); LOG2(21); LOG2(22); LOG2(23);
			    LOG2(24); LOG2(25); LOG2(26); LOG2(27);
			    LOG2(28); LOG2(29); LOG2(30); LOG2(31);
			}
			return -1; /* impossible... */
		}
	}

	return -1;
}


char *full_fname(const char *fn)
{
	static char *result = NULL;
	char *m1, *m2, *m3;
	char *p1, *p2;

	if (result)
		free(result);

	if (*fn == '/')
		p1 = p2 = "";
	else {
		p1 = curr_dir + module_dirlen;
		for (p2 = p1; *p2 == '/'; p2++) {}
		if (*p2)
			p2 = "/";
	}
	if (module_id >= 0) {
		m1 = " (in ";
		m2 = lp_name(module_id);
		m3 = ")";
	} else
		m1 = m2 = m3 = "";

	if (asprintf(&result, "\"%s%s%s\"%s%s%s", p1, p2, fn, m1, m2, m3) < 0)
		out_of_memory("full_fname");

	return result;
}


int handle_partial_dir(const char *fname, int create)
{
	char *fn, *dir;

	if (fname != partial_fname)
		return 1;
	if (!create && *partial_dir == '/')
		return 1;
	if (!(fn = strrchr(partial_fname, '/')))
		return 1;

	*fn = '\0';
	dir = partial_fname;
	if (create) {
		STRUCT_STAT st;
		int statret = do_lstat(dir, &st);
		if (statret == 0 && !S_ISDIR(st.st_mode)) {
			if (do_unlink(dir) < 0) {
				*fn = '/';
				return 0;
			}
			statret = -1;
		}
		if (statret < 0 && do_mkdir(dir, 0700) < 0) {
			*fn = '/';
			return 0;
		}
	} else
		do_rmdir(dir);
	*fn = '/';

	return 1;
}


const char *find_filename_suffix(const char *fn, int fn_len, int *len_ptr)
{
	const char *suf, *s;
	BOOL had_tilde;
	int s_len;

	/* One or more dots at the start aren't a suffix. */
	while (fn_len && *fn == '.') fn++, fn_len--;

	/* Ignore the ~ in a "foo~" filename. */
	if (fn_len > 1 && fn[fn_len-1] == '~')
		fn_len--, had_tilde = True;
	else
		had_tilde = False;

	/* Assume we don't find an suffix. */
	suf = "";
	*len_ptr = 0;

	/* Find the last significant suffix. */
	for (s = fn + fn_len; fn_len > 1; ) {
		while (*--s != '.' && s != fn) {}
		if (s == fn)
			break;
		s_len = fn_len - (s - fn);
		fn_len = s - fn;
		if (s_len == 4) {
			if (strcmp(s+1, "bak") == 0
			 || strcmp(s+1, "old") == 0)
				continue;
		} else if (s_len == 5) {
			if (strcmp(s+1, "orig") == 0)
				continue;
		} else if (s_len > 2 && had_tilde
		    && s[1] == '~' && isDigit(s + 2))
			continue;
		*len_ptr = s_len;
		suf = s;
		if (s_len == 1)
			break;
		/* Determine if the suffix is all digits. */
		for (s++, s_len--; s_len > 0; s++, s_len--) {
			if (!isDigit(s))
				return suf;
		}
		/* An all-digit suffix may not be that signficant. */
		s = suf;
	}

	return suf;
}


int mbedtls_asn1_write_int( unsigned char **p, unsigned char *start, int val )
{
    int ret;
    size_t len = 0;

    // TODO negative values and values larger than 128
    // DER format assumes 2s complement for numbers, so the leftmost bit
    // should be 0 for positive numbers and 1 for negative numbers.
    //
    if( *p - start < 1 )
        return( MBEDTLS_ERR_ASN1_BUF_TOO_SMALL );

    len += 1;
    *--(*p) = val;

    if( val > 0 && **p & 0x80 )
    {
        if( *p - start < 1 )
            return( MBEDTLS_ERR_ASN1_BUF_TOO_SMALL );

        *--(*p) = 0x00;
        len += 1;
    }

    MBEDTLS_ASN1_CHK_ADD( len, mbedtls_asn1_write_len( p, start, len ) );
    MBEDTLS_ASN1_CHK_ADD( len, mbedtls_asn1_write_tag( p, start, MBEDTLS_ASN1_INTEGER ) );

    return( (int) len );
}


int mbedtls_asn1_write_mpi( unsigned char **p, unsigned char *start, const mbedtls_mpi *X )
{
    int ret;
    size_t len = 0;

    // Write the MPI
    //
    len = mbedtls_mpi_size( X );

    if( *p < start || (size_t)( *p - start ) < len )
        return( MBEDTLS_ERR_ASN1_BUF_TOO_SMALL );

    (*p) -= len;
    MBEDTLS_MPI_CHK( mbedtls_mpi_write_binary( X, *p, len ) );

    // DER format assumes 2s complement for numbers, so the leftmost bit
    // should be 0 for positive numbers and 1 for negative numbers.
    //
    if( X->s ==1 && **p & 0x80 )
    {
        if( *p - start < 1 )
            return( MBEDTLS_ERR_ASN1_BUF_TOO_SMALL );

        *--(*p) = 0x00;
        len += 1;
    }

    MBEDTLS_ASN1_CHK_ADD( len, mbedtls_asn1_write_len( p, start, len ) );
    MBEDTLS_ASN1_CHK_ADD( len, mbedtls_asn1_write_tag( p, start, MBEDTLS_ASN1_INTEGER ) );

    ret = (int) len;

cleanup:
    return( ret );
}


peer_clear(
	struct peer *peer,		/* peer structure */
	const char *ident		/* tally lights */
	)
{
	u_char	u;

#ifdef AUTOKEY
	/*
	 * If cryptographic credentials have been acquired, toss them to
	 * Valhalla. Note that autokeys are ephemeral, in that they are
	 * tossed immediately upon use. Therefore, the keylist can be
	 * purged anytime without needing to preserve random keys. Note
	 * that, if the peer is purged, the cryptographic variables are
	 * purged, too. This makes it much harder to sneak in some
	 * unauthenticated data in the clock filter.
	 */
	key_expire(peer);
	if (peer->iffval != NULL)
		BN_free(peer->iffval);
	value_free(&peer->cookval);
	value_free(&peer->recval);
	value_free(&peer->encrypt);
	value_free(&peer->sndval);
	if (peer->cmmd != NULL)
		free(peer->cmmd);
	if (peer->subject != NULL)
		free(peer->subject);
	if (peer->issuer != NULL)
		free(peer->issuer);
#endif /* AUTOKEY */

	/*
	 * Clear all values, including the optional crypto values above.
	 */
	memset(CLEAR_TO_ZERO(peer), 0, LEN_CLEAR_TO_ZERO(peer));
	peer->ppoll = peer->maxpoll;
	peer->hpoll = peer->minpoll;
	peer->disp = MAXDISPERSE;
	peer->flash = peer_unfit(peer);
	peer->jitter = LOGTOD(sys_precision);

	/*
	 * If interleave mode, initialize the alternate origin switch.
	 */
	if (peer->flags & FLAG_XLEAVE)
		peer->flip = 1;
	for (u = 0; u < NTP_SHIFT; u++) {
		peer->filter_order[u] = u;
		peer->filter_disp[u] = MAXDISPERSE;
	}
#ifdef REFCLOCK
	if (!(peer->flags & FLAG_REFCLOCK)) {
#endif
		peer->leap = LEAP_NOTINSYNC;
		peer->stratum = STRATUM_UNSPEC;
		memcpy(&peer->refid, ident, 4);
#ifdef REFCLOCK
	}
#endif

	/*
	 * During initialization use the association count to spread out
	 * the polls at one-second intervals. Passive associations'
	 * first poll is delayed by the "discard minimum" to avoid rate
	 * limiting. Other post-startup new or cleared associations
	 * randomize the first poll over the minimum poll interval to
	 * avoid implosion.
	 */
	peer->nextdate = peer->update = peer->outdate = current_time;
	if (initializing) {
		peer->nextdate += peer_associations;
	} else if (MODE_PASSIVE == peer->hmode) {
		peer->nextdate += ntp_minpkt;
	} else {
		peer->nextdate += ntp_random() % peer->minpoll;
	}
#ifdef AUTOKEY
	peer->refresh = current_time + (1 << NTP_REFRESH);
#endif	/* AUTOKEY */
	DPRINTF(1, ("peer_clear: at %ld next %ld associd %d refid %s\n",
		    current_time, peer->nextdate, peer->associd,
		    ident));
}


get_broadcastclient_flag(void)
{
	return (broadcast_client_enabled);
}


addr_samesubnet(
	const sockaddr_u *	a,
	const sockaddr_u *	a_mask,
	const sockaddr_u *	b,
	const sockaddr_u *	b_mask
	)
{
	const u_int32 *	pa;
	const u_int32 *	pa_limit;
	const u_int32 *	pb;
	const u_int32 *	pm;
	size_t		loops;

	REQUIRE(AF(a) == AF(a_mask));
	REQUIRE(AF(b) == AF(b_mask));
	/*
	 * With address and mask families verified to match, comparing
	 * the masks also validates the address's families match.
	 */
	if (!SOCK_EQ(a_mask, b_mask))
		return FALSE;

	if (IS_IPV6(a)) {
		loops = sizeof(NSRCADR6(a)) / sizeof(*pa);
		pa = (const void *)&NSRCADR6(a);
		pb = (const void *)&NSRCADR6(b);
		pm = (const void *)&NSRCADR6(a_mask);
	} else {
		loops = sizeof(NSRCADR(a)) / sizeof(*pa);
		pa = (const void *)&NSRCADR(a);
		pb = (const void *)&NSRCADR(b);
		pm = (const void *)&NSRCADR(a_mask);
	}
	for (pa_limit = pa + loops; pa < pa_limit; pa++, pb++, pm++)
		if ((*pa & *pm) != (*pb & *pm))
			return FALSE;

	return TRUE;
}


AddTrustedKey(keyid_t keyno)
{
	/*
	 * We need to add a MD5-key in addition to setting the
	 * trust, because authhavekey() requires type != 0.
	 */
	MD5auth_setkey(keyno, KEYTYPE, NULL, 0, NULL);

	authtrust(keyno, TRUE);

	return;
}


measure_tick_fuzz(void)
{
	l_fp	minstep;	/* MINSTEP as l_fp */
	l_fp	val;		/* current seconds fraction */
	l_fp	last;		/* last seconds fraction */
	l_fp	ldiff;		/* val - last */
	double	tick;		/* computed tick value */
	double	diff;
	long	repeats;
	long	max_repeats;
	int	changes;
	int	i;		/* log2 precision */

	tick = MAXSTEP;
	max_repeats = 0;
	repeats = 0;
	changes = 0;
	DTOLFP(MINSTEP, &minstep);
	get_systime(&last);
	for (i = 0; i < MAXLOOPS && changes < MINCHANGES; i++) {
		get_systime(&val);
		ldiff = val;
		L_SUB(&ldiff, &last);
		last = val;
		if (L_ISGT(&ldiff, &minstep)) {
			max_repeats = max(repeats, max_repeats);
			repeats = 0;
			changes++;
			LFPTOD(&ldiff, diff);
			tick = min(diff, tick);
		} else {
			repeats++;
		}
	}
	if (changes < MINCHANGES) {
		msyslog(LOG_ERR, "Fatal error: precision could not be measured (MINSTEP too large?)");
		exit(1);
	}

	if (0 == max_repeats) {
		set_sys_fuzz(tick);
	} else {
		set_sys_fuzz(tick / max_repeats);
	}

	return tick;
}


authhavekey(
	keyid_t		id
	)
{
	symkey *	sk;

	authkeylookups++;
	if (0 == id || cache_keyid == id) {
		return TRUE;
	}

	/*
	 * Seach the bin for the key. If found and the key type
	 * is zero, somebody marked it trusted without specifying
	 * a key or key type. In this case consider the key missing.
	 */
	authkeyuncached++;
	for (sk = key_hash[KEYHASH(id)]; sk != NULL; sk = sk->hlink) {
		if (id == sk->keyid) {
			if (0 == sk->type) {
				authkeynotfound++;
				return FALSE;
			}
			break;
		}
	}

	/*
	 * If the key is not found, or if it is found but not trusted,
	 * the key is not considered found.
	 */
	if (NULL == sk) {
		authkeynotfound++;
		return FALSE;
	}
	if (!(KEY_TRUSTED & sk->flags)) {
		authnokey++;
		return FALSE;
	}

	/*
	 * The key is found and trusted. Initialize the key cache.
	 */
	cache_keyid = sk->keyid;
	cache_type = sk->type;
	cache_flags = sk->flags;
	cache_secret = sk->secret;
	cache_secretsize = sk->secretsize;
	cache_keyacclist = sk->keyacclist;

	return TRUE;
}


AddUntrustedKey(keyid_t keyno)
{
	authtrust(keyno, FALSE);

	return;
}


authdecrypt(
	keyid_t		keyno,
	u_int32 *	pkt,
	size_t		length,
	size_t		size
	)
{
	/*
	 * A zero key identifier means the sender has not verified
	 * the last message was correctly authenticated.  For our
	 * purpose this is an invalid authenticator.
	 */
	authdecryptions++;
	if (0 == keyno || !authhavekey(keyno) || size < 4) {
		return FALSE;
	}

	return MD5authdecrypt(cache_type, cache_secret, pkt, length,
			      size);
}


enable_broadcast(
	struct interface *	iface,
	sockaddr_u *		baddr
	)
{
#ifdef OPEN_BCAST_SOCKET
	socket_broadcast_enable(iface, iface->fd, baddr);
#endif
}


session_key(
	sockaddr_u *srcadr, 	/* source address */
	sockaddr_u *dstadr, 	/* destination address */
	keyid_t	keyno,		/* key ID */
	keyid_t	private,	/* private value */
	u_long	lifetime 	/* key lifetime */
	)
{
	EVP_MD_CTX ctx;		/* message digest context */
	u_char dgst[EVP_MAX_MD_SIZE]; /* message digest */
	keyid_t	keyid;		/* key identifer */
	u_int32	header[10];	/* data in network byte order */
	u_int	hdlen, len;

	if (!dstadr)
		return 0;

	/*
	 * Generate the session key and key ID. If the lifetime is
	 * greater than zero, install the key and call it trusted.
	 */
	hdlen = 0;
	switch(AF(srcadr)) {
	case AF_INET:
		header[0] = NSRCADR(srcadr);
		header[1] = NSRCADR(dstadr);
		header[2] = htonl(keyno);
		header[3] = htonl(private);
		hdlen = 4 * sizeof(u_int32);
		break;

	case AF_INET6:
		memcpy(&header[0], PSOCK_ADDR6(srcadr),
		    sizeof(struct in6_addr));
		memcpy(&header[4], PSOCK_ADDR6(dstadr),
		    sizeof(struct in6_addr));
		header[8] = htonl(keyno);
		header[9] = htonl(private);
		hdlen = 10 * sizeof(u_int32);
		break;
	}
	EVP_DigestInit(&ctx, EVP_get_digestbynid(crypto_nid));
	EVP_DigestUpdate(&ctx, (u_char *)header, hdlen);
	EVP_DigestFinal(&ctx, dgst, &len);
	memcpy(&keyid, dgst, 4);
	keyid = ntohl(keyid);
	if (lifetime != 0) {
		MD5auth_setkey(keyno, crypto_nid, dgst, len, NULL);
		authtrust(keyno, lifetime);
	}
	DPRINTF(2, ("session_key: %s > %s %08x %08x hash %08x life %lu\n",
		    stoa(srcadr), stoa(dstadr), keyno,
		    private, keyid, lifetime));

	return (keyid);
}


auth_log2(
	size_t x)
{
	int	s;
	int	r = 0;
	size_t  m = ~(size_t)0;

	for (s = sizeof(size_t) / 2 * CHAR_BIT; s != 0; s >>= 1) {
		m <<= s;
		if (x & m)
			r += s;
		else
			x <<= s;
	}
	return (u_short)r;
}


crypto_recv(
	struct peer *peer,	/* peer structure pointer */
	struct recvbuf *rbufp	/* packet buffer pointer */
	)
{
	const EVP_MD *dp;	/* message digest algorithm */
	u_int32	*pkt;		/* receive packet pointer */
	struct autokey *ap, *bp; /* autokey pointer */
	struct exten *ep, *fp;	/* extension pointers */
	struct cert_info *xinfo; /* certificate info pointer */
	int	macbytes;	/* length of MAC field, signed by intention */
	int	authlen;	/* offset of MAC field */
	associd_t associd;	/* association ID */
	tstamp_t fstamp = 0;	/* filestamp */
	u_int	len;		/* extension field length */
	u_int	code;		/* extension field opcode */
	u_int	vallen = 0;	/* value length */
	X509	*cert;		/* X509 certificate */
	char	statstr[NTP_MAXSTRLEN]; /* statistics for filegen */
	keyid_t	cookie;		/* crumbles */
	int	hismode;	/* packet mode */
	int	rval = XEVNT_OK;
	const u_char *puch;
	u_int32 temp32;

	/*
	 * Initialize. Note that the packet has already been checked for
	 * valid format and extension field lengths. First extract the
	 * field length, command code and association ID in host byte
	 * order. These are used with all commands and modes. Then check
	 * the version number, which must be 2, and length, which must
	 * be at least 8 for requests and VALUE_LEN (24) for responses.
	 * Packets that fail either test sink without a trace. The
	 * association ID is saved only if nonzero.
	 */
	authlen = LEN_PKT_NOMAC;
	hismode = (int)PKT_MODE((&rbufp->recv_pkt)->li_vn_mode);
	while ((macbytes = rbufp->recv_length - authlen) > (int)MAX_MAC_LEN) {
		/* We can be reasonably sure that we can read at least
		 * the opcode and the size field here. More stringent
		 * checks follow up shortly.
		 */
		pkt = (u_int32 *)&rbufp->recv_pkt + authlen / 4;
		ep = (struct exten *)pkt;
		code = ntohl(ep->opcode) & 0xffff0000;
		len = ntohl(ep->opcode) & 0x0000ffff;
		// HMS: Why pkt[1] instead of ep->associd ?
		associd = (associd_t)ntohl(pkt[1]);
		rval = XEVNT_OK;
		DPRINTF(1, ("crypto_recv: flags 0x%x ext offset %d len %u code 0x%x associd %d\n",
			    peer->crypto, authlen, len, code >> 16,
			    associd));

		/*
		 * Check version number and field length. If bad,
		 * quietly ignore the packet.
		 */
		if (((code >> 24) & 0x3f) != CRYPTO_VN || len < 8) {
			sys_badlength++;
			code |= CRYPTO_ERROR;
		}

		/* Check if the declared size fits into the remaining
		 * buffer. We *know* 'macbytes' > 0 here!
		 */
		if (len > (u_int)macbytes) {
			DPRINTF(1, ("crypto_recv: possible attack detected, associd %d\n",
				    associd));
			return XEVNT_LEN;
		}

		/* Check if the paylod of the extension fits into the
		 * declared frame.
		 */
		if (len >= VALUE_LEN) {
			fstamp = ntohl(ep->fstamp);
			vallen = ntohl(ep->vallen);
			/*
			 * Bug 2761: I hope this isn't too early...
			 */
			if (   vallen == 0
			    || len - VALUE_LEN < vallen)
				return XEVNT_LEN;
		}
		switch (code) {

		/*
		 * Install status word, host name, signature scheme and
		 * association ID. In OpenSSL the signature algorithm is
		 * bound to the digest algorithm, so the NID completely
		 * defines the signature scheme. Note the request and
		 * response are identical, but neither is validated by
		 * signature. The request is processed here only in
		 * symmetric modes. The server name field might be
		 * useful to implement access controls in future.
		 */
		case CRYPTO_ASSOC:

			/*
			 * If our state machine is running when this
			 * message arrives, the other fellow might have
			 * restarted. However, this could be an
			 * intruder, so just clamp the poll interval and
			 * find out for ourselves. Otherwise, pass the
			 * extension field to the transmit side.
			 */
			if (peer->crypto & CRYPTO_FLAG_CERT) {
				rval = XEVNT_ERR;
				break;
			}
			if (peer->cmmd) {
				if (peer->assoc != associd) {
					rval = XEVNT_ERR;
					break;
				}
				free(peer->cmmd); /* will be set again! */
			}
			fp = emalloc(len);
			memcpy(fp, ep, len);
			fp->associd = htonl(peer->associd);
			peer->cmmd = fp;
			/* fall through */

		case CRYPTO_ASSOC | CRYPTO_RESP:

			/*
			 * Discard the message if it has already been
			 * stored or the message has been amputated.
			 */
			if (peer->crypto) {
				if (peer->assoc != associd)
					rval = XEVNT_ERR;
				break;
			}
			INSIST(len >= VALUE_LEN);
			if (vallen == 0 || vallen > MAXHOSTNAME ||
			    len - VALUE_LEN < vallen) {
				rval = XEVNT_LEN;
				break;
			}
			DPRINTF(1, ("crypto_recv: ident host 0x%x %d server 0x%x %d\n",
				    crypto_flags, peer->associd, fstamp,
				    peer->assoc));
			temp32 = crypto_flags & CRYPTO_FLAG_MASK;

			/*
			 * If the client scheme is PC, the server scheme
			 * must be PC. The public key and identity are
			 * presumed valid, so we skip the certificate
			 * and identity exchanges and move immediately
			 * to the cookie exchange which confirms the
			 * server signature.
			 */
			if (crypto_flags & CRYPTO_FLAG_PRIV) {
				if (!(fstamp & CRYPTO_FLAG_PRIV)) {
					rval = XEVNT_KEY;
					break;
				}
				fstamp |= CRYPTO_FLAG_CERT |
				    CRYPTO_FLAG_VRFY | CRYPTO_FLAG_SIGN;

			/*
			 * It is an error if either peer supports
			 * identity, but the other does not.
			 */
			} else if (hismode == MODE_ACTIVE || hismode ==
			    MODE_PASSIVE) {
				if ((temp32 && !(fstamp &
				    CRYPTO_FLAG_MASK)) ||
				    (!temp32 && (fstamp &
				    CRYPTO_FLAG_MASK))) {
					rval = XEVNT_KEY;
					break;
				}
			}

			/*
			 * Discard the message if the signature digest
			 * NID is not supported.
			 */
			temp32 = (fstamp >> 16) & 0xffff;
			dp =
			    (const EVP_MD *)EVP_get_digestbynid(temp32);
			if (dp == NULL) {
				rval = XEVNT_MD;
				break;
			}

			/*
			 * Save status word, host name and message
			 * digest/signature type. If this is from a
			 * broadcast and the association ID has changed,
			 * request the autokey values.
			 */
			peer->assoc = associd;
			if (hismode == MODE_SERVER)
				fstamp |= CRYPTO_FLAG_AUTO;
			if (!(fstamp & CRYPTO_FLAG_TAI))
				fstamp |= CRYPTO_FLAG_LEAP;
			RAND_bytes((u_char *)&peer->hcookie, 4);
			peer->crypto = fstamp;
			peer->digest = dp;
			if (peer->subject != NULL)
				free(peer->subject);
			peer->subject = emalloc(vallen + 1);
			memcpy(peer->subject, ep->pkt, vallen);
			peer->subject[vallen] = '\0';
			if (peer->issuer != NULL)
				free(peer->issuer);
			peer->issuer = estrdup(peer->subject);
			snprintf(statstr, sizeof(statstr),
			    "assoc %d %d host %s %s", peer->associd,
			    peer->assoc, peer->subject,
			    OBJ_nid2ln(temp32));
			record_crypto_stats(&peer->srcadr, statstr);
			DPRINTF(1, ("crypto_recv: %s\n", statstr));
			break;

		/*
		 * Decode X509 certificate in ASN.1 format and extract
		 * the data containing, among other things, subject
		 * name and public key. In the default identification
		 * scheme, the certificate trail is followed to a self
		 * signed trusted certificate.
		 */
		case CRYPTO_CERT | CRYPTO_RESP:

			/*
			 * Discard the message if empty or invalid.
			 */
			if (len < VALUE_LEN)
				break;

			if ((rval = crypto_verify(ep, NULL, peer)) !=
			    XEVNT_OK)
				break;

			/*
			 * Scan the certificate list to delete old
			 * versions and link the newest version first on
			 * the list. Then, verify the signature. If the
			 * certificate is bad or missing, just ignore
			 * it.
			 */
			if ((xinfo = cert_install(ep, peer)) == NULL) {
				rval = XEVNT_CRT;
				break;
			}
			if ((rval = cert_hike(peer, xinfo)) != XEVNT_OK)
				break;

			/*
			 * We plug in the public key and lifetime from
			 * the first certificate received. However, note
			 * that this certificate might not be signed by
			 * the server, so we can't check the
			 * signature/digest NID.
			 */
			if (peer->pkey == NULL) {
				puch = xinfo->cert.ptr;
				cert = d2i_X509(NULL, &puch,
				    ntohl(xinfo->cert.vallen));
				peer->pkey = X509_get_pubkey(cert);
				X509_free(cert);
			}
			peer->flash &= ~TEST8;
			temp32 = xinfo->nid;
			snprintf(statstr, sizeof(statstr),
			    "cert %s %s 0x%x %s (%u) fs %u",
			    xinfo->subject, xinfo->issuer, xinfo->flags,
			    OBJ_nid2ln(temp32), temp32,
			    ntohl(ep->fstamp));
			record_crypto_stats(&peer->srcadr, statstr);
			DPRINTF(1, ("crypto_recv: %s\n", statstr));
			break;

		/*
		 * Schnorr (IFF) identity scheme. This scheme is
		 * designed for use with shared secret server group keys
		 * and where the certificate may be generated by a third
		 * party. The client sends a challenge to the server,
		 * which performs a calculation and returns the result.
		 * A positive result is possible only if both client and
		 * server contain the same secret group key.
		 */
		case CRYPTO_IFF | CRYPTO_RESP:

			/*
			 * Discard the message if invalid.
			 */
			if ((rval = crypto_verify(ep, NULL, peer)) !=
			    XEVNT_OK)
				break;

			/*
			 * If the challenge matches the response, the
			 * server public key, signature and identity are
			 * all verified at the same time. The server is
			 * declared trusted, so we skip further
			 * certificate exchanges and move immediately to
			 * the cookie exchange.
			 */
			if ((rval = crypto_iff(ep, peer)) != XEVNT_OK)
				break;

			peer->crypto |= CRYPTO_FLAG_VRFY;
			peer->flash &= ~TEST8;
			snprintf(statstr, sizeof(statstr), "iff %s fs %u",
			    peer->issuer, ntohl(ep->fstamp));
			record_crypto_stats(&peer->srcadr, statstr);
			DPRINTF(1, ("crypto_recv: %s\n", statstr));
			break;

		/*
		 * Guillou-Quisquater (GQ) identity scheme. This scheme
		 * is designed for use with public certificates carrying
		 * the GQ public key in an extension field. The client
		 * sends a challenge to the server, which performs a
		 * calculation and returns the result. A positive result
		 * is possible only if both client and server contain
		 * the same group key and the server has the matching GQ
		 * private key.
		 */
		case CRYPTO_GQ | CRYPTO_RESP:

			/*
			 * Discard the message if invalid
			 */
			if ((rval = crypto_verify(ep, NULL, peer)) !=
			    XEVNT_OK)
				break;

			/*
			 * If the challenge matches the response, the
			 * server public key, signature and identity are
			 * all verified at the same time. The server is
			 * declared trusted, so we skip further
			 * certificate exchanges and move immediately to
			 * the cookie exchange.
			 */
			if ((rval = crypto_gq(ep, peer)) != XEVNT_OK)
				break;

			peer->crypto |= CRYPTO_FLAG_VRFY;
			peer->flash &= ~TEST8;
			snprintf(statstr, sizeof(statstr), "gq %s fs %u",
			    peer->issuer, ntohl(ep->fstamp));
			record_crypto_stats(&peer->srcadr, statstr);
			DPRINTF(1, ("crypto_recv: %s\n", statstr));
			break;

		/*
		 * Mu-Varadharajan (MV) identity scheme. This scheme is
		 * designed for use with three levels of trust, trusted
		 * host, server and client. The trusted host key is
		 * opaque to servers and clients; the server keys are
		 * opaque to clients and each client key is different.
		 * Client keys can be revoked without requiring new key
		 * generations.
		 */
		case CRYPTO_MV | CRYPTO_RESP:

			/*
			 * Discard the message if invalid.
			 */
			if ((rval = crypto_verify(ep, NULL, peer)) !=
			    XEVNT_OK)
				break;

			/*
			 * If the challenge matches the response, the
			 * server public key, signature and identity are
			 * all verified at the same time. The server is
			 * declared trusted, so we skip further
			 * certificate exchanges and move immediately to
			 * the cookie exchange.
			 */
			if ((rval = crypto_mv(ep, peer)) != XEVNT_OK)
				break;

			peer->crypto |= CRYPTO_FLAG_VRFY;
			peer->flash &= ~TEST8;
			snprintf(statstr, sizeof(statstr), "mv %s fs %u",
			    peer->issuer, ntohl(ep->fstamp));
			record_crypto_stats(&peer->srcadr, statstr);
			DPRINTF(1, ("crypto_recv: %s\n", statstr));
			break;

		/*
		 * Cookie response in client and symmetric modes. If the
		 * cookie bit is set, the working cookie is the EXOR of
		 * the current and new values.
		 */
		case CRYPTO_COOK | CRYPTO_RESP:

			/*
			 * Discard the message if invalid or signature
			 * not verified with respect to the cookie
			 * values.
			 */
			if ((rval = crypto_verify(ep, &peer->cookval,
			    peer)) != XEVNT_OK)
				break;

			/*
			 * Decrypt the cookie, hunting all the time for
			 * errors.
			 */
			if (vallen == (u_int)EVP_PKEY_size(host_pkey)) {
				u_int32 *cookiebuf = malloc(
				    RSA_size(host_pkey->pkey.rsa));
				if (!cookiebuf) {
					rval = XEVNT_CKY;
					break;
				}

				if (RSA_private_decrypt(vallen,
				    (u_char *)ep->pkt,
				    (u_char *)cookiebuf,
				    host_pkey->pkey.rsa,
				    RSA_PKCS1_OAEP_PADDING) != 4) {
					rval = XEVNT_CKY;
					free(cookiebuf);
					break;
				} else {
					cookie = ntohl(*cookiebuf);
					free(cookiebuf);
				}
			} else {
				rval = XEVNT_CKY;
				break;
			}

			/*
			 * Install cookie values and light the cookie
			 * bit. If this is not broadcast client mode, we
			 * are done here.
			 */
			key_expire(peer);
			if (hismode == MODE_ACTIVE || hismode ==
			    MODE_PASSIVE)
				peer->pcookie = peer->hcookie ^ cookie;
			else
				peer->pcookie = cookie;
			peer->crypto |= CRYPTO_FLAG_COOK;
			peer->flash &= ~TEST8;
			snprintf(statstr, sizeof(statstr),
			    "cook %x ts %u fs %u", peer->pcookie,
			    ntohl(ep->tstamp), ntohl(ep->fstamp));
			record_crypto_stats(&peer->srcadr, statstr);
			DPRINTF(1, ("crypto_recv: %s\n", statstr));
			break;

		/*
		 * Install autokey values in broadcast client and
		 * symmetric modes. We have to do this every time the
		 * sever/peer cookie changes or a new keylist is
		 * rolled. Ordinarily, this is automatic as this message
		 * is piggybacked on the first NTP packet sent upon
		 * either of these events. Note that a broadcast client
		 * or symmetric peer can receive this response without a
		 * matching request.
		 */
		case CRYPTO_AUTO | CRYPTO_RESP:

			/*
			 * Discard the message if invalid or signature
			 * not verified with respect to the receive
			 * autokey values.
			 */
			if ((rval = crypto_verify(ep, &peer->recval,
			    peer)) != XEVNT_OK)
				break;

			/*
			 * Discard the message if a broadcast client and
			 * the association ID does not match. This might
			 * happen if a broacast server restarts the
			 * protocol. A protocol restart will occur at
			 * the next ASSOC message.
			 */
			if ((peer->cast_flags & MDF_BCLNT) &&
			    peer->assoc != associd)
				break;

			/*
			 * Install autokey values and light the
			 * autokey bit. This is not hard.
			 */
			if (ep->tstamp == 0)
				break;

			if (peer->recval.ptr == NULL)
				peer->recval.ptr =
				    emalloc(sizeof(struct autokey));
			bp = (struct autokey *)peer->recval.ptr;
			peer->recval.tstamp = ep->tstamp;
			peer->recval.fstamp = ep->fstamp;
			ap = (struct autokey *)ep->pkt;
			bp->seq = ntohl(ap->seq);
			bp->key = ntohl(ap->key);
			peer->pkeyid = bp->key;
			peer->crypto |= CRYPTO_FLAG_AUTO;
			peer->flash &= ~TEST8;
			snprintf(statstr, sizeof(statstr),
			    "auto seq %d key %x ts %u fs %u", bp->seq,
			    bp->key, ntohl(ep->tstamp),
			    ntohl(ep->fstamp));
			record_crypto_stats(&peer->srcadr, statstr);
			DPRINTF(1, ("crypto_recv: %s\n", statstr));
			break;

		/*
		 * X509 certificate sign response. Validate the
		 * certificate signed by the server and install. Later
		 * this can be provided to clients of this server in
		 * lieu of the self signed certificate in order to
		 * validate the public key.
		 */
		case CRYPTO_SIGN | CRYPTO_RESP:

			/*
			 * Discard the message if invalid.
			 */
			if ((rval = crypto_verify(ep, NULL, peer)) !=
			    XEVNT_OK)
				break;

			/*
			 * Scan the certificate list to delete old
			 * versions and link the newest version first on
			 * the list.
			 */
			if ((xinfo = cert_install(ep, peer)) == NULL) {
				rval = XEVNT_CRT;
				break;
			}
			peer->crypto |= CRYPTO_FLAG_SIGN;
			peer->flash &= ~TEST8;
			temp32 = xinfo->nid;
			snprintf(statstr, sizeof(statstr),
			    "sign %s %s 0x%x %s (%u) fs %u",
			    xinfo->subject, xinfo->issuer, xinfo->flags,
			    OBJ_nid2ln(temp32), temp32,
			    ntohl(ep->fstamp));
			record_crypto_stats(&peer->srcadr, statstr);
			DPRINTF(1, ("crypto_recv: %s\n", statstr));
			break;

		/*
		 * Install leapseconds values. While the leapsecond
		 * values epoch, TAI offset and values expiration epoch
		 * are retained, only the current TAI offset is provided
		 * via the kernel to other applications.
		 */
		case CRYPTO_LEAP | CRYPTO_RESP:
			/*
			 * Discard the message if invalid. We can't
			 * compare the value timestamps here, as they
			 * can be updated by different servers.
			 */
			rval = crypto_verify(ep, NULL, peer);
			if ((rval   != XEVNT_OK          ) ||
			    (vallen != 3*sizeof(uint32_t))  )
				break;

			/* Check if we can update the basic TAI offset
			 * for our current leap frame. This is a hack
			 * and ignores the time stamps in the autokey
			 * message.
			 */
			if (sys_leap != LEAP_NOTINSYNC)
				leapsec_autokey_tai(ntohl(ep->pkt[0]),
						    rbufp->recv_time.l_ui, NULL);
			tai_leap.tstamp = ep->tstamp;
			tai_leap.fstamp = ep->fstamp;
			crypto_update();
			mprintf_event(EVNT_TAI, peer,
				      "%d seconds", ntohl(ep->pkt[0]));
			peer->crypto |= CRYPTO_FLAG_LEAP;
			peer->flash &= ~TEST8;
			snprintf(statstr, sizeof(statstr),
				 "leap TAI offset %d at %u expire %u fs %u",
				 ntohl(ep->pkt[0]), ntohl(ep->pkt[1]),
				 ntohl(ep->pkt[2]), ntohl(ep->fstamp));
			record_crypto_stats(&peer->srcadr, statstr);
			DPRINTF(1, ("crypto_recv: %s\n", statstr));
			break;

		/*
		 * We come here in symmetric modes for miscellaneous
		 * commands that have value fields but are processed on
		 * the transmit side. All we need do here is check for
		 * valid field length. Note that ASSOC is handled
		 * separately.
		 */
		case CRYPTO_CERT:
		case CRYPTO_IFF:
		case CRYPTO_GQ:
		case CRYPTO_MV:
		case CRYPTO_COOK:
		case CRYPTO_SIGN:
			if (len < VALUE_LEN) {
				rval = XEVNT_LEN;
				break;
			}
			/* fall through */

		/*
		 * We come here in symmetric modes for requests
		 * requiring a response (above plus AUTO and LEAP) and
		 * for responses. If a request, save the extension field
		 * for later; invalid requests will be caught on the
		 * transmit side. If an error or invalid response,
		 * declare a protocol error.
		 */
		default:
			if (code & (CRYPTO_RESP | CRYPTO_ERROR)) {
				rval = XEVNT_ERR;
			} else if (peer->cmmd == NULL) {
				fp = emalloc(len);
				memcpy(fp, ep, len);
				peer->cmmd = fp;
			}
		}

		/*
		 * The first error found terminates the extension field
		 * scan and we return the laundry to the caller.
		 */
		if (rval != XEVNT_OK) {
			snprintf(statstr, sizeof(statstr),
			    "%04x %d %02x %s", htonl(ep->opcode),
			    associd, rval, eventstr(rval));
			record_crypto_stats(&peer->srcadr, statstr);
			DPRINTF(1, ("crypto_recv: %s\n", statstr));
			return (rval);
		}
		authlen += (len + 3) / 4 * 4;
	}
	return (rval);
}