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

#pragma once

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

namespace zeek::analyzer::login {

enum login_state : uint8_t {
    LOGIN_STATE_AUTHENTICATE, // trying to authenticate
    LOGIN_STATE_LOGGED_IN,    // successful authentication
    LOGIN_STATE_SKIP,         // skip any further processing
    LOGIN_STATE_CONFUSED,     // we're confused
};

// If no action by this many lines, we're definitely confused.
constexpr int MAX_AUTHENTICATE_LINES = 50;

// Maximum # lines look after login for failure.
constexpr int MAX_LOGIN_LOOKAHEAD = 10;

// If we have more user text than this unprocessed, we complain about
// excessive typeahead.
constexpr int MAX_USER_TEXT = 12;

class Login_Analyzer : public analyzer::tcp::TCP_ApplicationAnalyzer {
public:
    Login_Analyzer(const char* name, Connection* conn);
    ~Login_Analyzer() override;

    void DeliverStream(int len, const u_char* data, bool orig) override;

    void SetEnv(bool orig, char* name, char* val) override;

    login_state LoginState() const { return state; }
    void SetLoginState(login_state s) { state = s; }

    void EndpointEOF(bool is_orig) override;

protected:
    void NewLine(bool orig, char* line);
    void AuthenticationDialog(bool orig, char* line);

    void LoginEvent(EventHandlerPtr f, const char* line, bool no_user_okay = false);
    const char* GetUsername(const char* line) const;
    void LineEvent(EventHandlerPtr f, const char* line);
    void Confused(const char* msg, const char* addl);
    void ConfusionText(const char* line);

    bool IsPloy(const char* line);
    bool IsSkipAuthentication(const char* line) const;
    const char* IsLoginPrompt(const char* line) const; // nil if not
    bool IsDirectLoginPrompt(const char* line) const;
    bool IsFailureMsg(const char* line) const;
    bool IsSuccessMsg(const char* line) const;
    bool IsTimeout(const char* line) const;
    bool IsEmpty(const char* line) const;

    void AddUserText(const char* line); // complains on overflow
    char* PeekUserText();               // internal warning on underflow
    char* PopUserText();                // internal warning on underflow
    Val* PopUserTextVal();

    bool MatchesTypeahead(const char* line) const;
    bool HaveTypeahead() const { return num_user_text > 0; }
    void FlushEmptyTypeahead();

    char* user_text[MAX_USER_TEXT] = {nullptr};
    int user_text_first, user_text_last; // indices into user_text
    int num_user_text;                   // number of entries in user_text

    Val* username;    // last username reported
    Val* client_name; // rlogin client name (or nil if none)

    login_state state;
    int lines_scanned;
    int num_user_lines_seen;
    int last_failure_num_user_lines;
    int login_prompt_line;
    int failure_line;

    bool is_VMS;
    bool saw_ploy;
};

} // namespace zeek::analyzer::login
