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

#pragma once

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

constexpr uint8_t TELNET_OPTION_BINARY = 0;
constexpr uint8_t TELNET_OPTION_TERMINAL = 24;
constexpr uint8_t TELNET_OPTION_AUTHENTICATE = 37;
constexpr uint8_t TELNET_OPTION_ENCRYPT = 38;
constexpr uint8_t TELNET_OPTION_ENVIRON = 39;
constexpr uint8_t NUM_TELNET_OPTIONS = 5;

namespace zeek::analyzer::login {

class NVT_Analyzer;

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

    // Whether we told the other side WILL/WONT/DO/DONT.
    enum SaidOptions : uint8_t { OPT_SAID_WILL = 0x1, OPT_SAID_WONT = 0x2, OPT_SAID_DO = 0x4, 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 = nullptr;
    unsigned int code;
    int flags = 0;
    bool active = false;
};

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] = {nullptr};
    int num_options = 0;
};

} // namespace zeek::analyzer::login
