/*
** Zabbix
** Copyright (C) 2001-2023 Zabbix SIA
**
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation; either version 2 of the License, or
** (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software
** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
**/

package zbxnet

import (
	"fmt"
	"net"
	"testing"

	"zabbix.com/internal/agent"
)

var testCases = []testCase{
	{1, "IPv4 match", "127.0.0.1", "127.0.0.1", false},
	{2, "Second IPv4 from list matches", "127.0.0.2", "127.0.0.1,127.0.0.2", false},
	{3, "Peer IP is different", "127.0.0.2", "127.0.0.1", true},
	{4, "Peer IP is different", "126.0.0.1", "127.0.0.1,127.0.0.2", true},
	{5, "IPv6 match", "2001:0db8:0000:0000:0000:ff00:0042:8329", "2001:0db8:0000:0000:0000:ff00:0042:8329", false},
	{6, "IPv6 from list matches", "2001:0db8:0000:0000:0000:ff00:0042:8329",
		"2001:0db8:0000:0000:0000:ff00:0042:8330,127.0.0.1,2001:0db8:0000:0000:0000:ff00:0042:8329", false},
	{7, "Peer IPv6 is different", "2001:0db8:0000:0000:0000:ff00:0042:8330", "2001:0db8:0000:0000:0000:ff00:0042:8329", true},
	{8, "Peer IPv6 is different at start", "3001:0db8:0000:0000:0000:ff00:0042:8330", "2001:0db8:0000:0000:0000:ff00:0042:8329", true},
	{9, "Peer IP is not in list", "2001:0db8:0000:0000:0000:ff00:0042:8329",
		"3001:0db8:0000:0000:0000:ff00:0042:8329,2101:0db8:0000:0000:0000:ff00:0042:8329,2001:0db9:0000:0000:0000:ff00:0042:8329," +
			"2001:0db8:0100:0000:0000:ff00:0042:8329,2001:0db8:0000:0001:0000:ff00:0042:8329,2001:0db8:0000:0000:0000:ff10:0042:8329," +
			"2001:0db8:0000:0000:0000:ff00:0043:8329", true},
	{10, "IPv6 compatible peer is connected", "::127.2.0.1", "127.2.0.1", true},
	{11, "IPv6 compatible expanded peer is connected", "0:0:0:0:0:0:7F00:0001", "127.0.0.1", true},
	{12, "IPv6 mapped peer is connected", "::ffff:127.0.0.1", "127.0.0.1", false},
	{13, "IPv6 mapped peer expanded is connected", "0:0:0:0:0:FFFF:7F00:0001", "127.0.0.1", false},
	{14, "IPv6 compatible peer mismatch IP", "::127.2.0.1", "127.1.0.1", true},
	{15, "IPv6 compatible expanded mismatch", "0:0:0:0:0:0:7F02:0001", "127.0.0.1", true},
	{16, "IPv6 mapped peer mismatch IP", "::ffff:127.0.0.1", "127.1.0.1", true},
	{17, "IPv6 mapped peer expanded mismatch IP", "0:0:0:0:0:FFFF:7F00:0001", "127.1.0.1", true},
	{18, "IPv6 peer partially compatible", "::fffe:127.0.0.1", "127.0.0.1", true},
	{19, "IPv6 peer does not match IPv4", "2001:0db8:0000:0000:0000:ff00:0042:8329", "127.0.0.1", true},
	{20, "IPv6 compatible expanded peer is connected, not in list", "F000:0:0:0:0:0:7F00:0001", "127.0.0.1", true},
	{21, "IPv6 compatible expanded peer is connected mismatch", "0000:0001:0:0:0:0:7F00:0001", "127.0.0.1", true},
	{22, "IPv6 mapped expanded is connected mismatch", "FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:7F00:0001", "127.0.0.1", true},
	{23, "IPv6 local ip mismatch IPv4 local IP", "::1", "127.0.0.1", true},
	{24, "IPv4 local IP expected, but IPv6 local IP expanded connected", "0:0:0:0:0:0:0:0001", "127.0.0.1", true},
	{25, "IPv4 compatible peer is connected", "127.2.0.1", "::127.2.0.1", true},
	{26, "IPv4 compatible expanded peer is connected", "127.0.0.1", "0:0:0:0:0:0:7F00:0001", true},
	{27, "IPv4 mapped peer is connected", "127.0.0.1", "::ffff:127.0.0.1", false},
	{28, "IPv4 mapped peer expanded is connected", "127.0.0.1", "0:0:0:0:0:FFFF:7F00:0001", false},
	{29, "IPv4 compatible peer mismatch IP", "127.1.0.1", "::127.2.0.1", true},
	{30, "IPv4 compatible expanded mismatch", "127.0.0.1", "0:0:0:0:0:0:7F02:0001", true},
	{31, "IPv4 mapped peer mismatch IP", "127.1.0.1", "::ffff:127.0.0.1", true},
	{32, "IPv4 mapped peer expanded mismatch IP", "127.1.0.1", "0:0:0:0:0:FFFF:7F00:0001", true},
	{33, "IPv4 peer partially compatible", "127.0.0.1", "::fffe:127.0.0.1", true},
	{34, "IPv4 peer does not match IPv6", "127.0.0.1", "2001:0db8:0000:0000:0000:ff00:0042:8329", true},
	{35, "IPv4 compatible expanded peer is connected, not in list", "127.0.0.1", "F000:0:0:0:0:0:7F00:0001", true},
	{36, "IPv4 compatible expanded peer is connected mismatch", "127.0.0.1", "0000:0001:0:0:0:0:7F00:0001", true},
	{37, "IPv4 mapped expanded is connected mismatch", "127.0.0.1", "FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:7F00:0001", true},
	{38, "IPv4 local IP mismatch IPv6 local IP", "127.0.0.1", "::1", true},
	{39, "IPv6 local expanded IP expected, but IPv4 local IP connected", "127.0.0.1", "0:0:0:0:0:0:0:0001", true},
	{40, "Compare only first 3 octets", "127.0.0.1", "127.0.0.0/24", false},
	{41, "Compare all 4 octets sanity check", "127.0.0.1", "127.0.0.0/32", true},
	{42, "IPv4 does not match address that is not compatible or mapped", "2001:0db8:0000:0000:0000:ff00:0042:8329", "127.0.0.0/0", true},
	{43, "IPv4 does not match address that is not compatible or mapped 2", "0:0:0:0:0:ff00:0042:8329", "127.0.0.0/0", true},
	{44, "IPv4 match address that is compatible or mapped", "::1", "127.0.0.0/0", true},
	{45, "Compare only first 96 bits", "0:0:0:0:0:0:0:0001", "0:0:0:0:0:0:ffff:ffff/96", false},
	{46, "Compare 128 bits", "0:0:0:0:0:0:0:0001", "0:0:0:0:0:0:ffff:ffff/128", true},
	{47, "Compare only the first 3 octets where the first one does not match", "128.0.0.1", "127.0.0.1/24", true},
	{48, "Compare only the first 96 bits where the first one does not match", "1:0:0:0:0:0:0:0001", "0:0:0:0:0:0:ffff:ffff/96", true},
	{49, "IPv4 in list", "127.0.0.1", "128.0.0.0/24,127.0.0.1", false},
	{50, "IPv6 in list", "::1", "1000:0000:0000:0000:0000:0000:FFFF:FFFF/96,::1", false},
	{51, "Any IPv4", "127.0.0.1", "128.0.0.0/0", false},
	{52, "Any IPv6", "::1", "1000:0000:0000:0000:0000:0000:FFFF:FFFF/0", false},
	{53, "Any IPv6 allows also any IPv4", "127.0.0.1", "1000:0000:0000:0000:0000:0000:FFFF:FFFF/0", false},
	{54, "IPv4 first CIDR value is not saved on next value in list", "128.0.0.1", "127.0.0.0/24,128.0.0.2", true},
	{55, "Long list of allowed peers and no match", "127.2.1.5",
		"localhost,127.0.0.2,127.0.0.0/24,0000:0000:0000:0000:0000:0000:127.0.0.1,::FFFF:127.0.0.1,0000:0000:0000:0000:0000:0000:0000:0003," +
			"::127.0.0.1,::127.0.0.1/128,::1", true},
	{56, "Long list of allowed peers and no match IPv6", "2001:0db8:0000:0000:0000:ff00:0042:8329",
		"localhost,127.0.0.2,127.0.0.0/24,0000:0000:0000:0000:0000:0000:127.0.0.1,::FFFF:127.0.0.1,0000:0000:0000:0000:0000:0000:0000:0003," +
			"::127.0.0.1,::127.0.0.1/128,::1", true},
	{57, "Long list of allowed peers but there is match", "127.2.1.5",
		"localhost,127.0.0.2,127.0.0.0/24,0000:0000:0000:0000:0000:0000:127.0.0.1,::FFFF:127.0.0.1,0000:0000:0000:0000:0000:0000:0000:0003," +
			"::127.0.0.1,::127.0.0.1/128,::1,::1/96,::,::/0,0.0.0.0/0,0000:0000:0000:0000:0000:0000:0000:0000/0", false},
	{58, "Long list of allowed peers but there is match IPv6", "2001:0db8:0000:0000:0000:ff00:0042:8329",
		"localhost,127.0.0.2,127.0.0.0/24,0000:0000:0000:0000:0000:0000:127.0.0.1,::FFFF:127.0.0.1,0000:0000:0000:0000:0000:0000:0000:0003," +
			"::127.0.0.1,::127.0.0.1/128,::1,::1/96,::,::/0,0.0.0.0/0,0000:0000:0000:0000:0000:0000:0000:0000/0", false},
	{59, "IPv6 unspecified address in list, connection from IPv6", "::1", "::", true},
	{60, "IPv6 unspecified address in list, connection from IPv4", "127.0.0.1", "::", true},
	{61, "DNS name as allowed address", "127.0.0.1", "localhost", false},
	{62, "DNS name as denied address", "127.0.0.2", "localhost", true},
	{63, "DNS name as denied IPv6 address", "::2", "localhost", true},
}

type testCase struct {
	id      uint
	name    string
	peer    string
	allowed string
	fail    bool
}

func TestAllowedPeers(t *testing.T) {
	for _, testCase := range testCases {
		if err := testCase.checkResult(); err != nil {
			t.Errorf("Test case \"%s\" (nr: %d), peer='%s', allowed_peers='%s' failed, error: %s)",
				testCase.name, testCase.id, testCase.peer, testCase.allowed, err.Error())
		}
	}
}

func (tc *testCase) checkResult() error {
	var options agent.AgentOptions

	options.Server = tc.allowed
	peerip := net.ParseIP(tc.peer)

	if ap, err := GetAllowedPeers(options.Server); err == nil {
		allow := ap.CheckPeer(peerip)

		if allow != true && tc.fail == false {
			return fmt.Errorf("peer is not allowed")
		} else if allow != false && tc.fail == true {
			return fmt.Errorf("peer is allowed while should not be")
		}
	}

	return nil
}