// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * Copyright (C) 2023 Amnesh Inc.
 *                    Mahdi Varasteh
 */

#include <zebra.h>
#include <sys/stat.h>

#ifdef CRYPTO_OPENSSL
#include <openssl/evp.h>
#include <openssl/hmac.h>
#endif

#include "linklist.h"
#include "if.h"
#include "checksum.h"

#include "ospfd/ospfd.h"
#include "ospfd/ospf_errors.h"
#include "ospfd/ospf_interface.h"
#include "ospfd/ospf_asbr.h"
#include "ospfd/ospf_lsa.h"
#include "ospfd/ospf_lsdb.h"
#include "ospfd/ospf_neighbor.h"
#include "ospfd/ospf_auth.h"
#include "ospfd/ospf_nsm.h"
#include "ospfd/ospf_dump.h"
#include "ospfd/ospf_packet.h"
#include "ospfd/ospf_network.h"
#include "ospfd/ospf_flood.h"
#include "ospfd/ospf_dump.h"
#include "ospfd/ospf_bfd.h"
#include "ospfd/ospf_gr.h"
#ifdef CRYPTO_INTERNAL
#include "sha256.h"
#include "md5.h"
#endif

const uint8_t ospf_auth_apad[KEYCHAIN_MAX_HASH_SIZE] = {
	0x87, 0x8F, 0xE1, 0xF3, 0x87, 0x8F, 0xE1, 0xF3, 0x87, 0x8F, 0xE1,
	0xF3, 0x87, 0x8F, 0xE1, 0xF3, 0x87, 0x8F, 0xE1, 0xF3, 0x87, 0x8F,
	0xE1, 0xF3, 0x87, 0x8F, 0xE1, 0xF3, 0x87, 0x8F, 0xE1, 0xF3, 0x87,
	0x8F, 0xE1, 0xF3, 0x87, 0x8F, 0xE1, 0xF3, 0x87, 0x8F, 0xE1, 0xF3,
	0x87, 0x8F, 0xE1, 0xF3, 0x87, 0x8F, 0xE1, 0xF3, 0x87, 0x8F, 0xE1,
	0xF3, 0x87, 0x8F, 0xE1, 0xF3, 0x87, 0x8F, 0xE1, 0xF3
};

static int ospf_check_sum(struct ospf_header *ospfh)
{
	uint32_t ret;
	uint16_t sum;

	/* clear auth_data for checksum. */
	memset(ospfh->u.auth_data, 0, OSPF_AUTH_SIMPLE_SIZE);

	/* keep checksum and clear. */
	sum = ospfh->checksum;
	memset(&ospfh->checksum, 0, sizeof(uint16_t));

	/* calculate checksum. */
	ret = in_cksum(ospfh, ntohs(ospfh->length));

	if (ret != sum) {
		zlog_info("%s: checksum mismatch, my %X, his %X", __func__, ret,
			  sum);
		return 0;
	}

	return 1;
}

#ifdef CRYPTO_OPENSSL
static const EVP_MD *ospf_auth_get_openssl_evp_md_from_key(struct key *key)
{
	if (key->hash_algo == KEYCHAIN_ALGO_HMAC_SHA1)
		return EVP_get_digestbyname("sha1");
	else if (key->hash_algo == KEYCHAIN_ALGO_HMAC_SHA256)
		return EVP_get_digestbyname("sha256");
	else if (key->hash_algo == KEYCHAIN_ALGO_HMAC_SHA384)
		return EVP_get_digestbyname("sha384");
	else if (key->hash_algo == KEYCHAIN_ALGO_HMAC_SHA512)
		return EVP_get_digestbyname("sha512");
	return NULL;
}
#endif

static int ospf_auth_check_hmac_sha_digest(struct ospf_interface *oi,
					   struct ospf_header *ospfh,
					   struct ip *iph,
					   struct key *key)
{
	unsigned char digest[KEYCHAIN_MAX_HASH_SIZE];
	struct ospf_neighbor *nbr;
	uint16_t length = ntohs(ospfh->length);
	uint16_t hash_length = keychain_get_hash_len(key->hash_algo);
#ifdef CRYPTO_OPENSSL
	uint32_t openssl_hash_length = hash_length;
	HMAC_CTX *ctx;
	const EVP_MD *md_alg = ospf_auth_get_openssl_evp_md_from_key(key);

	if (!md_alg) {
		flog_warn(EC_OSPF_AUTH,
			  "interface %s: invalid HMAC algorithm, Router-ID: %pI4",
			  IF_NAME(oi), &ospfh->router_id);
		return 0;
	}
#elif CRYPTO_INTERNAL
	HMAC_SHA256_CTX ctx;

	if (key->hash_algo != KEYCHAIN_ALGO_HMAC_SHA256) {
		flog_warn(EC_OSPF_AUTH,
			  "interface %s: HMAC algorithm not supported, Router-ID: %pI4",
			  IF_NAME(oi), &ospfh->router_id);
		return 0;
	}
#endif
	/* check crypto seqnum. */
	nbr = ospf_nbr_lookup(oi, iph, ospfh);

	if (nbr &&
	    ntohl(nbr->crypt_seqnum) > ntohl(ospfh->u.crypt.crypt_seqnum)) {
		flog_warn(EC_OSPF_AUTH,
			  "interface %s: ospf_check_hmac_sha bad sequence %u (expect %d), Router-ID: %pI4",
			  IF_NAME(oi), ntohl(ospfh->u.crypt.crypt_seqnum),
			  ntohl(nbr->crypt_seqnum), &ospfh->router_id);
		return 0;
	}
#ifdef CRYPTO_OPENSSL
	ctx = HMAC_CTX_new();
	HMAC_Init_ex(ctx, key->string, strlen(key->string), md_alg, NULL);
	HMAC_Update(ctx, (const unsigned char *)ospfh, length);
	HMAC_Update(ctx, (const unsigned char *)ospf_auth_apad, hash_length);
	HMAC_Final(ctx, digest, &openssl_hash_length);
	HMAC_CTX_free(ctx);
#elif CRYPTO_INTERNAL
	memset(&ctx, 0, sizeof(ctx));
	HMAC__SHA256_Init(&ctx, key->string, strlen(key->string));
	HMAC__SHA256_Update(&ctx, ospfh, length);
	HMAC__SHA256_Update(&ctx, ospf_auth_apad, hash_length);
	HMAC__SHA256_Final(digest, &ctx);
#endif
	if (memcmp((caddr_t)ospfh + length, digest, hash_length)) {
		flog_warn(EC_OSPF_AUTH,
			  "interface %s: ospf_check_hmac_sha checksum mismatch %u, Router-ID: %pI4",
			  IF_NAME(oi), length, &ospfh->router_id);
		return 0;
	}
	if (nbr)
		nbr->crypt_seqnum = ospfh->u.crypt.crypt_seqnum;
	return 1;
}

static int ospf_auth_check_md5_digest(struct ospf_interface *oi,
				      struct ospf_header *ospfh, struct ip *iph, struct key *key)
{
#ifdef CRYPTO_OPENSSL
	EVP_MD_CTX *ctx;
#elif CRYPTO_INTERNAL
	MD5_CTX ctx;
#endif
	char auth_key[OSPF_AUTH_MD5_SIZE + 1];
	unsigned char digest[OSPF_AUTH_MD5_SIZE];
	struct ospf_neighbor *nbr;
	struct crypt_key *ck = NULL;
	uint16_t length = ntohs(ospfh->length);

	if (length < sizeof(struct ospf_header)) {/* for coverity's sake */
		flog_warn(EC_OSPF_AUTH,
			  "%s: Invalid packet length of %u received on interface %s, Router-ID: %pI4",
			  __func__, length, IF_NAME(oi), &ospfh->router_id);
		return 0;
	}

	if (key == NULL) {
		ck = ospf_crypt_key_lookup(OSPF_IF_PARAM(oi, auth_crypt),
				   ospfh->u.crypt.key_id);
		if (ck == NULL) {
			flog_warn(
				EC_OSPF_AUTH,
				"interface %s: %s no key %d, Router-ID: %pI4",
				IF_NAME(oi), __func__, ospfh->u.crypt.key_id, &ospfh->router_id);
			return 0;
		}
	}
	/* check crypto seqnum. */
	nbr = ospf_nbr_lookup(oi, iph, ospfh);

	if (nbr &&
	    ntohl(nbr->crypt_seqnum) > ntohl(ospfh->u.crypt.crypt_seqnum)) {
		flog_warn(EC_OSPF_AUTH,
			  "interface %s: %s bad sequence %d (expect %d), Router-ID: %pI4",
			  IF_NAME(oi), __func__, ntohl(ospfh->u.crypt.crypt_seqnum),
			  ntohl(nbr->crypt_seqnum), &ospfh->router_id);
		return 0;
	}

	memset(auth_key, 0, OSPF_AUTH_MD5_SIZE + 1);
	if (ck == NULL)
		strlcpy(auth_key, key->string, OSPF_AUTH_MD5_SIZE + 1);
	else
		strlcpy(auth_key, (char *)ck->auth_key, OSPF_AUTH_MD5_SIZE + 1);
	/* Generate a digest for the ospf packet - their digest + our digest. */
#ifdef CRYPTO_OPENSSL
	uint32_t md5_size = OSPF_AUTH_MD5_SIZE;

	ctx = EVP_MD_CTX_new();
	EVP_DigestInit(ctx, EVP_md5());
	EVP_DigestUpdate(ctx, ospfh, length);
	EVP_DigestUpdate(ctx, auth_key, OSPF_AUTH_MD5_SIZE);
	EVP_DigestFinal(ctx, digest, &md5_size);
	EVP_MD_CTX_free(ctx);
#elif CRYPTO_INTERNAL
	memset(&ctx, 0, sizeof(ctx));
	MD5Init(&ctx);
	MD5Update(&ctx, ospfh, length);
	MD5Update(&ctx, auth_key, OSPF_AUTH_MD5_SIZE);
	MD5Final(digest, &ctx);
#endif

	/* compare the two */
	if (memcmp((caddr_t)ospfh + length, digest, OSPF_AUTH_MD5_SIZE)) {
		flog_warn(EC_OSPF_AUTH,
			  "interface %s: ospf_check_md5 checksum mismatch, Router-ID: %pI4",
			  IF_NAME(oi), &ospfh->router_id);
		return 0;
	}

	/* save neighbor's crypt_seqnum */
	if (nbr)
		nbr->crypt_seqnum = ospfh->u.crypt.crypt_seqnum;
	return 1;
}

static int ospf_auth_make_md5_digest(struct ospf_interface *oi,
				     struct ospf_packet *op, struct key *key)
{
	void *ibuf = STREAM_DATA(op->s);
	struct ospf_header *ospfh = (struct ospf_header *)ibuf;
	unsigned char digest[OSPF_AUTH_MD5_SIZE];
	uint16_t length = ntohs(ospfh->length);
#ifdef CRYPTO_OPENSSL
	EVP_MD_CTX *ctx;
#elif CRYPTO_INTERNAL
	MD5_CTX ctx;
#endif
	char auth_key[OSPF_AUTH_MD5_SIZE + 1];

	if ((length < (sizeof(struct ospf_header))) || (length > op->length)) { /* for coverity's sake */
		flog_warn(EC_OSPF_AUTH,
			  "%s: Invalid packet length of %u received on interface %s, Router-ID: %pI4",
			  __func__, length, IF_NAME(oi), &ospfh->router_id);
		return 0;
	}

	memset(auth_key, 0, OSPF_AUTH_MD5_SIZE + 1);
	strlcpy(auth_key, key->string, OSPF_AUTH_MD5_SIZE + 1);
	/* Generate a digest for the ospf packet - their digest + our digest. */
#ifdef CRYPTO_OPENSSL
	uint32_t md5_size = OSPF_AUTH_MD5_SIZE;

	ctx = EVP_MD_CTX_new();
	EVP_DigestInit(ctx, EVP_md5());
	EVP_DigestUpdate(ctx, ospfh, length);
	EVP_DigestUpdate(ctx, auth_key, OSPF_AUTH_MD5_SIZE);
	EVP_DigestFinal(ctx, digest, &md5_size);
	EVP_MD_CTX_free(ctx);
#elif CRYPTO_INTERNAL
	memset(&ctx, 0, sizeof(ctx));
	MD5Init(&ctx);
	MD5Update(&ctx, ospfh, length);
	MD5Update(&ctx, auth_key, OSPF_AUTH_MD5_SIZE);
	MD5Final(digest, &ctx);
#endif

	stream_put(op->s, digest, OSPF_AUTH_MD5_SIZE);

	op->length = ntohs(ospfh->length) + OSPF_AUTH_MD5_SIZE;

	if (stream_get_endp(op->s) != op->length)
		/* XXX size_t */
		flog_warn(EC_OSPF_AUTH,
			  "%s: length mismatch stream %lu ospf_packet %u, Router-ID %pI4",
			  __func__, (unsigned long)stream_get_endp(op->s),
			  op->length, &ospfh->router_id);

	return OSPF_AUTH_MD5_SIZE;
}

static int ospf_auth_make_hmac_sha_digest(struct ospf_interface *oi,
					  struct ospf_packet *op,
					  struct key *key)
{
	void *ibuf;
	struct ospf_header *ospfh;
	unsigned char digest[KEYCHAIN_MAX_HASH_SIZE] = { 0 };
	uint16_t hash_length = keychain_get_hash_len(key->hash_algo);

	ibuf = STREAM_DATA(op->s);
	ospfh = (struct ospf_header *)ibuf;
#ifdef CRYPTO_OPENSSL
	uint32_t openssl_hash_length = hash_length;
	HMAC_CTX *ctx;
	const EVP_MD *md_alg = ospf_auth_get_openssl_evp_md_from_key(key);

	if (!md_alg) {
		flog_warn(EC_OSPF_AUTH,
			  "interface %s: invalid HMAC algorithm, Router-ID: %pI4",
			  IF_NAME(oi), &ospfh->router_id);
		return 0;
	}
#elif CRYPTO_INTERNAL
	HMAC_SHA256_CTX ctx;

	if (key->hash_algo != KEYCHAIN_ALGO_HMAC_SHA256) {
		flog_warn(EC_OSPF_AUTH,
			  "interface %s: HMAC algorithm not supported, Router-ID: %pI4",
			  IF_NAME(oi), &ospfh->router_id);
		return 0;
	}
#endif
#ifdef CRYPTO_OPENSSL
	ctx = HMAC_CTX_new();
	HMAC_Init_ex(ctx, key->string, strlen(key->string), md_alg, NULL);
	HMAC_Update(ctx, (const unsigned char *)ospfh, ntohs(ospfh->length));
	HMAC_Update(ctx, (const unsigned char *)ospf_auth_apad, hash_length);
	HMAC_Final(ctx, digest, &openssl_hash_length);
	HMAC_CTX_free(ctx);
#elif CRYPTO_INTERNAL
	memset(&ctx, 0, sizeof(ctx));
	HMAC__SHA256_Init(&ctx, key->string, strlen(key->string));
	HMAC__SHA256_Update(&ctx, ospfh, ntohs(ospfh->length));
	HMAC__SHA256_Update(&ctx, ospf_auth_apad, hash_length);
	HMAC__SHA256_Final(digest, &ctx);
#endif
	stream_put(op->s, digest, hash_length);

	op->length = ntohs(ospfh->length) + hash_length;

	if (stream_get_endp(op->s) != op->length)
		/* XXX size_t */
		flog_warn(EC_OSPF_AUTH,
			  "%s: length mismatch stream %lu ospf_packet %u, Router-ID %pI4",
			  __func__, (unsigned long)stream_get_endp(op->s),
			  op->length, &ospfh->router_id);

	return hash_length;
}

int ospf_auth_check_digest(struct ospf_interface *oi, struct ip *iph, struct ospf_header *ospfh)
{
	struct keychain *keychain = NULL;
	struct key *key = NULL;
	int key_id = ospfh->u.crypt.key_id;
	uint8_t auth_data_len = ospfh->u.crypt.auth_data_len;

	if (!OSPF_IF_PARAM(oi, keychain_name))
		return ospf_auth_check_md5_digest(oi, ospfh, iph, NULL);

	keychain = keychain_lookup(OSPF_IF_PARAM(oi, keychain_name));
	if (!keychain) {
		if (IS_DEBUG_OSPF_PACKET(ospfh->type - 1, RECV))
			flog_warn(EC_OSPF_AUTH,
				  "interface %s: Keychain %s is not available, Router-ID %pI4",
				  IF_NAME(oi), OSPF_IF_PARAM(oi, keychain_name),
				  &ospfh->router_id);
		return 0;
	}

	key = key_lookup_for_accept(keychain, key_id);
	if (!key) {
		if (IS_DEBUG_OSPF_PACKET(ospfh->type - 1, RECV))
			flog_warn(EC_OSPF_AUTH,
				  "interface %s: Key ID %d not found in keychain %s, Router-ID %pI4",
				  IF_NAME(oi), key_id, keychain->name,
				  &ospfh->router_id);
		return 0;
	}

	if (key->string == NULL || key->hash_algo == KEYCHAIN_ALGO_NULL) {
		if (IS_DEBUG_OSPF_PACKET(ospfh->type - 1, RECV))
			flog_warn(EC_OSPF_AUTH,
				  "interface %s: Key ID %d in keychain %s is incomplete, Router-ID %pI4",
				  IF_NAME(oi), key_id, keychain->name,
				  &ospfh->router_id);
		return 0;
	}

	if (keychain_get_hash_len(key->hash_algo) != auth_data_len) {
		if (IS_DEBUG_OSPF_PACKET(ospfh->type - 1, RECV))
			flog_warn(EC_OSPF_AUTH,
				  "interface %s: Key ID %d in keychain %s hash length mismatch, Router-ID %pI4",
				  IF_NAME(oi), key_id, keychain->name,
				  &ospfh->router_id);
		return 0;
	}

	/* Backward compatibility with RFC 2328 keyed-MD5 authentication */
	if (key->hash_algo == KEYCHAIN_ALGO_MD5)
		return ospf_auth_check_md5_digest(oi, ospfh, iph, key);

	return ospf_auth_check_hmac_sha_digest(oi, ospfh, iph, key);
}

int ospf_auth_make_digest(struct ospf_interface *oi, struct ospf_packet *op)
{
	struct ospf_header *ospfh;
	void *ibuf;
	struct keychain *keychain = NULL;
	struct key *key = NULL;
	int key_id;

	ibuf = STREAM_DATA(op->s);
	ospfh = (struct ospf_header *)ibuf;

	key_id = ospfh->u.crypt.key_id;

	if (!OSPF_IF_PARAM(oi, keychain_name)) {
		if (IS_DEBUG_OSPF_PACKET(ospfh->type - 1, RECV))
			flog_warn(EC_OSPF_AUTH,
				  "interface %s: Keychain is not set, Router-ID %pI4",
				  IF_NAME(oi), &ospfh->router_id);
		return 0;
	}

	keychain = oi->keychain;
	if (!keychain) {
		if (IS_DEBUG_OSPF_PACKET(ospfh->type - 1, SEND))
			flog_warn(EC_OSPF_AUTH,
				  "interface %s: Keychain %s is not available to send, Router-ID %pI4",
				  IF_NAME(oi), OSPF_IF_PARAM(oi, keychain_name),
				  &ospfh->router_id);
		return 0;
	}

	key = oi->key;
	if (!key) {
		if (IS_DEBUG_OSPF_PACKET(ospfh->type - 1, SEND))
			flog_warn(EC_OSPF_AUTH,
				  "interface %s: Key ID %d not found in keychain %s, Router-ID %pI4",
				  IF_NAME(oi), key_id, keychain->name,
				  &ospfh->router_id);
		return 0;
	}

	if (key->string == NULL || key->hash_algo == KEYCHAIN_ALGO_NULL) {
		if (IS_DEBUG_OSPF_PACKET(ospfh->type - 1, SEND))
			flog_warn(EC_OSPF_AUTH,
				  "interface %s: Key ID %d in keychain %s is incomplete, Router-ID %pI4",
				  IF_NAME(oi), key_id, keychain->name,
				  &ospfh->router_id);
		return 0;
	}

	/* Backward compatibility with RFC 2328 keyed-MD5 authentication */
	if (key->hash_algo == KEYCHAIN_ALGO_MD5)
		return ospf_auth_make_md5_digest(oi, op, key);
	else
		return ospf_auth_make_hmac_sha_digest(oi, op, key);
}

/* This function is called from ospf_write(), it will detect the
 * authentication scheme and if it is MD5, it will change the sequence
 * and update the MD5 digest.
 */

int ospf_auth_make(struct ospf_interface *oi, struct ospf_packet *op)
{
	struct ospf_header *ospfh;
	unsigned char digest[OSPF_AUTH_MD5_SIZE] = {0};
#ifdef CRYPTO_OPENSSL
	EVP_MD_CTX *ctx;
#elif CRYPTO_INTERNAL
	MD5_CTX ctx;
#endif
	void *ibuf;
	uint32_t t;
	struct crypt_key *ck;
	const uint8_t *auth_key = NULL;

	ibuf = STREAM_DATA(op->s);
	ospfh = (struct ospf_header *)ibuf;

	if (ntohs(ospfh->auth_type) != OSPF_AUTH_CRYPTOGRAPHIC)
		return 0;

	/* We do this here so when we dup a packet, we don't have to
	 * waste CPU rewriting other headers.

	   Note that frr_time /deliberately/ is not used here.
	*/
	t = (time(NULL) & 0xFFFFFFFF);
	if (t > oi->crypt_seqnum)
		oi->crypt_seqnum = t;
	else
		oi->crypt_seqnum++;

	ospfh->u.crypt.crypt_seqnum = htonl(oi->crypt_seqnum);

	/* Get MD5 Authentication key from auth_key list. */
	if (list_isempty(OSPF_IF_PARAM(oi, auth_crypt)) && OSPF_IF_PARAM(oi, keychain_name) == NULL)
		auth_key = (const uint8_t *)digest;
	else if (!list_isempty(OSPF_IF_PARAM(oi, auth_crypt))) {
		ck = listgetdata(listtail(OSPF_IF_PARAM(oi, auth_crypt)));
		auth_key = ck->auth_key;
	}

	if (auth_key) {
		/* Generate a digest for the entire packet + our secret key. */
#ifdef CRYPTO_OPENSSL
		uint32_t md5_size = OSPF_AUTH_MD5_SIZE;

		ctx = EVP_MD_CTX_new();
		EVP_DigestInit(ctx, EVP_md5());
		EVP_DigestUpdate(ctx, ibuf, ntohs(ospfh->length));
		EVP_DigestUpdate(ctx, auth_key, OSPF_AUTH_MD5_SIZE);
		EVP_DigestFinal(ctx, digest, &md5_size);
		EVP_MD_CTX_free(ctx);
#elif CRYPTO_INTERNAL
		memset(&ctx, 0, sizeof(ctx));
		MD5Init(&ctx);
		MD5Update(&ctx, ibuf, ntohs(ospfh->length));
		MD5Update(&ctx, auth_key, OSPF_AUTH_MD5_SIZE);
		MD5Final(digest, &ctx);
#endif

		/* Append md5 digest to the end of the stream. */
		stream_put(op->s, digest, OSPF_AUTH_MD5_SIZE);

		/* We do *NOT* increment the OSPF header length. */
		op->length = ntohs(ospfh->length) + OSPF_AUTH_MD5_SIZE;

		if (stream_get_endp(op->s) != op->length)
			/* XXX size_t */
			flog_warn(
				EC_OSPF_AUTH,
				"%s: length mismatch stream %lu ospf_packet %u, Router-ID %pI4",
				__func__, (unsigned long)stream_get_endp(op->s),
				op->length, &ospfh->router_id);

		return OSPF_AUTH_MD5_SIZE;
	} else
		return ospf_auth_make_digest(oi, op);
}

/* Return 1, if the packet is properly authenticated and checksummed,
 * 0 otherwise. In particular, check that AuType header field is valid and
 * matches the locally configured AuType, and that D.5 requirements are met.
 */
int ospf_auth_check(struct ospf_interface *oi, struct ip *iph,
			struct ospf_header *ospfh)
{
	uint16_t iface_auth_type;
	uint16_t pkt_auth_type = ntohs(ospfh->auth_type);

	iface_auth_type = ospf_auth_type(oi);

	switch (pkt_auth_type) {
	case OSPF_AUTH_NULL: /* RFC2328 D.5.1 */
		if (iface_auth_type != OSPF_AUTH_NULL) {
			if (IS_DEBUG_OSPF_PACKET(ospfh->type - 1, RECV))
				flog_warn(
					EC_OSPF_PACKET,
					"interface %s: auth-type mismatch, local %s, rcvd Null, Router-ID %pI4",
					IF_NAME(oi),
					lookup_msg(ospf_auth_type_str,
						   iface_auth_type, NULL),
					&ospfh->router_id);
			return 0;
		}
		if (!ospf_check_sum(ospfh)) {
			if (IS_DEBUG_OSPF_PACKET(ospfh->type - 1, RECV))
				flog_warn(
					EC_OSPF_PACKET,
					"interface %s: Null auth OK, but checksum error, Router-ID %pI4",
					IF_NAME(oi),
					&ospfh->router_id);
			return 0;
		}
		return 1;
	case OSPF_AUTH_SIMPLE: /* RFC2328 D.5.2 */
		if (iface_auth_type != OSPF_AUTH_SIMPLE) {
			if (IS_DEBUG_OSPF_PACKET(ospfh->type - 1, RECV))
				flog_warn(
					EC_OSPF_PACKET,
					"interface %s: auth-type mismatch, local %s, rcvd Simple, Router-ID %pI4",
					IF_NAME(oi),
					lookup_msg(ospf_auth_type_str,
						   iface_auth_type, NULL),
					&ospfh->router_id);
			return 0;
		}
		if (memcmp(OSPF_IF_PARAM(oi, auth_simple), ospfh->u.auth_data,
			   OSPF_AUTH_SIMPLE_SIZE)) {
			if (IS_DEBUG_OSPF_PACKET(ospfh->type - 1, RECV))
				flog_warn(
					EC_OSPF_PACKET,
					"interface %s: Simple auth failed, Router-ID %pI4",
					IF_NAME(oi), &ospfh->router_id);
			return 0;
		}
		if (!ospf_check_sum(ospfh)) {
			if (IS_DEBUG_OSPF_PACKET(ospfh->type - 1, RECV))
				flog_warn(
					EC_OSPF_PACKET,
					"interface %s: Simple auth OK, checksum error, Router-ID %pI4",
					IF_NAME(oi),
					&ospfh->router_id);
			return 0;
		}
		return 1;
	case OSPF_AUTH_CRYPTOGRAPHIC: /* RFC2328 D.5.3 */
		if (iface_auth_type != OSPF_AUTH_CRYPTOGRAPHIC) {
			if (IS_DEBUG_OSPF_PACKET(ospfh->type - 1, RECV))
				flog_warn(
					EC_OSPF_PACKET,
					"interface %s: auth-type mismatch, local %s, rcvd Cryptographic, Router-ID %pI4",
					IF_NAME(oi),
					lookup_msg(ospf_auth_type_str,
						   iface_auth_type, NULL),
					&ospfh->router_id);
			return 0;
		}
		if (ospfh->checksum) {
			if (IS_DEBUG_OSPF_PACKET(ospfh->type - 1, RECV))
				flog_warn(
					EC_OSPF_PACKET,
					"interface %s: OSPF header checksum is not 0, Router-ID %pI4",
					IF_NAME(oi), &ospfh->router_id);
			return 0;
		}
		/* If `authentication message-digest` key is not set, we try keychain crypto */
		if (OSPF_IF_PARAM(oi, keychain_name) || !list_isempty(OSPF_IF_PARAM(oi, auth_crypt)))
			return ospf_auth_check_digest(oi, iph, ospfh);
		if (IS_DEBUG_OSPF_PACKET(ospfh->type - 1, RECV))
			flog_warn(
				EC_OSPF_AUTH,
				"interface %s: MD5 auth failed, Router-ID %pI4",
				IF_NAME(oi), &ospfh->router_id);
		return 0;
	default:
		if (IS_DEBUG_OSPF_PACKET(ospfh->type - 1, RECV))
			flog_warn(
				EC_OSPF_PACKET,
				"interface %s: invalid packet auth-type (%02x), Router-ID %pI4",
				IF_NAME(oi), pkt_auth_type, &ospfh->router_id);
		return 0;
	}
}

/* OSPF authentication checking function */
int ospf_auth_type(struct ospf_interface *oi)
{
	int auth_type;

	if (OSPF_IF_PARAM(oi, auth_type) == OSPF_AUTH_NOTSET)
		auth_type = oi->area->auth_type;
	else
		auth_type = OSPF_IF_PARAM(oi, auth_type);

	/* Handle case where MD5 key list, or a key-chain, is not configured aka Cisco */
	if (auth_type == OSPF_AUTH_CRYPTOGRAPHIC
	    && (list_isempty(OSPF_IF_PARAM(oi, auth_crypt))
		&& OSPF_IF_PARAM(oi, keychain_name) == NULL))
		return OSPF_AUTH_NULL;

	return auth_type;
}

/* Make Authentication Data. */
int ospf_auth_make_data(struct ospf_interface *oi, struct ospf_header *ospfh)
{
	struct crypt_key *ck;

	switch (ospf_auth_type(oi)) {
	case OSPF_AUTH_NULL:
		/* memset (ospfh->u.auth_data, 0, sizeof(ospfh->u.auth_data));
		 */
		break;
	case OSPF_AUTH_SIMPLE:
		memcpy(ospfh->u.auth_data, OSPF_IF_PARAM(oi, auth_simple),
		       OSPF_AUTH_SIMPLE_SIZE);
		break;
	case OSPF_AUTH_CRYPTOGRAPHIC:
		if (OSPF_IF_PARAM(oi, keychain_name)) {
			oi->keychain = keychain_lookup(OSPF_IF_PARAM(oi, keychain_name));
			if (oi->keychain)
				oi->key = key_lookup_for_send(oi->keychain);
			if (oi->key) {
				ospfh->u.crypt.zero = 0;
				ospfh->u.crypt.key_id = oi->key->index;
				ospfh->u.crypt.auth_data_len = keychain_get_hash_len(oi->key->hash_algo);
			} else {
				/* If key is not set, then set 0. */
				ospfh->u.crypt.zero = 0;
				ospfh->u.crypt.key_id = 0;
				ospfh->u.crypt.auth_data_len = OSPF_AUTH_MD5_SIZE;
			}
		} else {
			/* If key is not set, then set 0. */
			if (list_isempty(OSPF_IF_PARAM(oi, auth_crypt))) {
				ospfh->u.crypt.zero = 0;
				ospfh->u.crypt.key_id = 0;
				ospfh->u.crypt.auth_data_len = OSPF_AUTH_MD5_SIZE;
			} else {
				ck = listgetdata(
					listtail(OSPF_IF_PARAM(oi, auth_crypt)));
				ospfh->u.crypt.zero = 0;
				ospfh->u.crypt.key_id = ck->key_id;
				ospfh->u.crypt.auth_data_len = OSPF_AUTH_MD5_SIZE;
			}
		}
		/* note: the seq is done in ospf_auth_make() */
		break;
	default:
		/* memset (ospfh->u.auth_data, 0, sizeof(ospfh->u.auth_data));
		 */
		break;
	}

	return 0;
}
