// See the file "COPYING" in the main distribution directory for copyright.

#pragma once

#include "zeek/analyzer/protocol/tcp/ContentLine.h"

#define TELNET_OPTION_BINARY 0
#define TELNET_OPTION_TERMINAL 24
#define TELNET_OPTION_AUTHENTICATE 37
#define TELNET_OPTION_ENCRYPT 38
#define TELNET_OPTION_ENVIRON 39
#define NUM_TELNET_OPTIONS 5

namespace zeek::analyzer::login
	{

class NVT_Analyzer;

class TelnetOption
	{
public:
	TelnetOption(NVT_Analyzer* endp, unsigned int code);
	virtual ~TelnetOption() { }

// Whether we told the other side WILL/WONT/DO/DONT.
#define OPT_SAID_WILL 0x1
#define OPT_SAID_WONT 0x2
#define OPT_SAID_DO 0x4
#define OPT_SAID_DONT 0x8

	unsigned int Code() const { return code; }

	bool IsActive() const { return active; }

	bool SaidWill() const { return flags & OPT_SAID_WILL; }
	bool SaidWont() const { return flags & OPT_SAID_WONT; }
	bool SaidDo() const { return flags & OPT_SAID_DO; }
	bool SaidDont() const { return flags & OPT_SAID_DONT; }

	void SetWill() { flags |= OPT_SAID_WILL; }
	void SetWont() { flags |= OPT_SAID_WONT; }
	void SetDo() { flags |= OPT_SAID_DO; }
	void SetDont() { flags |= OPT_SAID_DONT; }

	void RecvOption(unsigned int type);
	virtual void RecvSubOption(u_char* data, int len);

	virtual void SetActive(bool is_active);

	const NVT_Analyzer* Endpoint() const { return endp; }

protected:
	friend class NVT_Analyzer;
	virtual void InconsistentOption(unsigned int type);
	virtual void BadOption();

	NVT_Analyzer* endp;
	unsigned int code;
	int flags;
	int active;
	};

namespace detail
	{

class TelnetTerminalOption final : public TelnetOption
	{
public:
	explicit TelnetTerminalOption(NVT_Analyzer* arg_endp)
		: TelnetOption(arg_endp, TELNET_OPTION_TERMINAL)
		{
		}

	void RecvSubOption(u_char* data, int len) override;
	};

class TelnetEncryptOption final : public TelnetOption
	{
public:
	explicit TelnetEncryptOption(NVT_Analyzer* arg_endp)
		: TelnetOption(arg_endp, TELNET_OPTION_ENCRYPT)
		{
		did_encrypt_request = doing_encryption = 0;
		}

	void RecvSubOption(u_char* data, int len) override;

	int DidRequest() const { return did_encrypt_request; }
	int DoingEncryption() const { return doing_encryption; }

protected:
	friend class NVT_Analyzer;
	int did_encrypt_request, doing_encryption;
	};

class TelnetAuthenticateOption final : public TelnetOption
	{
public:
	explicit TelnetAuthenticateOption(NVT_Analyzer* arg_endp)
		: TelnetOption(arg_endp, TELNET_OPTION_AUTHENTICATE)
		{
		authentication_requested = 0;
		}

	void RecvSubOption(u_char* data, int len) override;

	int DidRequestAuthentication() const { return authentication_requested; }

protected:
	friend class NVT_Analyzer;
	int authentication_requested;
	};

class TelnetEnvironmentOption final : public TelnetOption
	{
public:
	explicit TelnetEnvironmentOption(NVT_Analyzer* arg_endp)
		: TelnetOption(arg_endp, TELNET_OPTION_ENVIRON)
		{
		}

	void RecvSubOption(u_char* data, int len) override;

protected:
	char* ExtractEnv(u_char*& data, int& len, int& code);
	};

class TelnetBinaryOption final : public TelnetOption
	{
public:
	explicit TelnetBinaryOption(NVT_Analyzer* arg_endp)
		: TelnetOption(arg_endp, TELNET_OPTION_BINARY)
		{
		}

	void SetActive(bool is_active) override;

protected:
	void InconsistentOption(unsigned int type) override;
	};

	} // namespace detail

class NVT_Analyzer final : public analyzer::tcp::ContentLine_Analyzer
	{
public:
	NVT_Analyzer(Connection* conn, bool orig);
	~NVT_Analyzer() override;

	TelnetOption* FindOption(unsigned int code);
	TelnetOption* FindPeerOption(unsigned int code);

	void SetPeer(NVT_Analyzer* arg_peer) { peer = arg_peer; }

	void AuthenticationAccepted();
	void AuthenticationRejected();

	void SetTerminal(const u_char* terminal, int len);
	void SetBinaryMode(int mode) { binary_mode = mode; }
	void SetEncrypting(int mode);
	void SetAuthName(char* arg_auth_name)
		{
		delete[] auth_name;
		auth_name = arg_auth_name;
		}

	const char* AuthName() const { return auth_name; }
	int AuthenticationHasBeenAccepted() const { return authentication_has_been_accepted; }

protected:
	void DoDeliver(int len, const u_char* data) override;
	void DeliverChunk(int& len, const u_char*& data);

	void ScanOption(int& len, const u_char*& data);
	void SawOption(unsigned int code);
	void SawOption(unsigned int code, unsigned int subcode);
	void SawSubOption(const char* opt, int len);
	void BadOptionTermination(unsigned int code);
	const char* PeerAuthName() const;

	NVT_Analyzer* peer = nullptr;

	int IAC_pos = 0; // where the IAC was seen
	bool pending_IAC = false; // true if we're working on an option/IAC
	bool is_suboption = false; // true if current option is suboption
	bool last_was_IAC = false; // for scanning suboptions
	bool authentication_has_been_accepted = false; // if true, we accepted peer's authentication

	int binary_mode = 0;
	int encrypting_mode = 0;
	char* auth_name = nullptr;

	TelnetOption* options[NUM_TELNET_OPTIONS];
	int num_options = 0;
	};

	} // namespace zeek::analyzer::login
