// SPDX-License-Identifier: GPL-2.0-or-later
/* Virtual terminal [aka TeletYpe] interface routine
 * Copyright (C) 1997 Kunihiro Ishiguro
 */

#ifndef _ZEBRA_VTY_H
#define _ZEBRA_VTY_H

#include <sys/types.h>
#ifdef HAVE_LIBPCRE2_POSIX
#ifndef _FRR_PCRE2_POSIX
#define _FRR_PCRE2_POSIX
#include <pcre2posix.h>
#endif /* _FRR_PCRE2_POSIX */
#elif defined(HAVE_LIBPCREPOSIX)
#include <pcreposix.h>
#else
#include <regex.h>
#endif /* HAVE_LIBPCRE2_POSIX */

#include "frrevent.h"
#include "log.h"
#include "sockunion.h"
#include "qobj.h"
#include "compiler.h"
#include "northbound.h"
#include "zlog_live.h"
#include "libfrr.h"
#include "mgmt_fe_client.h"

#ifdef __cplusplus
extern "C" {
#endif

struct json_object;

#define VTY_BUFSIZ 4096
#define VTY_MAXHIST 20
#define VTY_MAXDEPTH 8

#define VTY_MAXCFGCHANGES 16

struct vty_error {
	char error_buf[VTY_BUFSIZ];
	uint32_t line_num;
	int cmd_ret;
};

struct vty_cfg_change {
	char xpath[XPATH_MAXLEN];
	enum nb_operation operation;
	const char *value;
};

PREDECL_DLIST(vtys);

/* VTY struct. */
struct vty {
	struct vtys_item itm;

	/* File descripter of this vty. */
	int fd;

	/* output FD, to support stdin/stdout combination */
	int wfd;

	/* File output, used for VTYSH only */
	FILE *of;
	FILE *of_saved;

	/* whether we are using pager or not */
	bool is_paged;

	/* Is this vty connect to file or not */
	enum { VTY_TERM,       /* telnet conn or stdin/stdout UI */
	       VTY_FILE,       /* reading and writing config files */
	       VTY_SHELL,      /* vtysh client side UI */
	       VTY_SHELL_SERV, /* server-side vtysh connection */
	} type;

	/* Node status of this vty */
	int node;

	/* Failure count */
	int fail;

	/* Output filer regex */
	bool filter;
	regex_t include;

	/* Line buffer */
	struct buffer *lbuf;

	/* Output buffer. */
	struct buffer *obuf;

	/* Command input buffer */
	char *buf;

	/* Command input error buffer */
	struct list *error;

	/* Command cursor point */
	int cp;

	/* Command length */
	int length;

	/* Command max length. */
	int max;

	/* Histry of command */
	char *hist[VTY_MAXHIST];

	/* History lookup current point */
	int hp;

	/* History insert end point */
	int hindex;

	/* Changes enqueued to be applied in the candidate configuration. */
	size_t num_cfg_changes;
	struct nb_cfg_change cfg_changes[VTY_MAXCFGCHANGES];

	/* XPath of the current node */
	int xpath_index;
	char xpath[VTY_MAXDEPTH][XPATH_MAXLEN];

	/*
	 * Keep track of how many SET_CFG requests has been sent so far that
	 * has not been committed yet.
	 */
	size_t mgmt_num_pending_setcfg;

	/* In configure mode. */
	bool config;

	/* Private candidate configuration mode. */
	bool private_config;

	/* Candidate configuration. */
	struct nb_config *candidate_config;

	/* Base candidate configuration. */
	struct nb_config *candidate_config_base;

	/* Dynamic transaction information. */
	bool pending_allowed;
	bool pending_commit;
	char *pending_cmds_buf;
	size_t pending_cmds_buflen;
	size_t pending_cmds_bufpos;

	/* Confirmed-commit timeout and rollback configuration. */
	struct event *t_confirmed_commit_timeout;
	struct nb_config *confirmed_commit_rollback;

	/* qobj object ID (replacement for "index") */
	uint64_t qobj_index;

	/* qobj second-level object ID (replacement for "index_sub") */
	uint64_t qobj_index_sub;

	/* For escape character. */
	unsigned char escape;

	/* Current vty status. */
	enum {
		VTY_NORMAL,
		VTY_CLOSE,
		VTY_MORE,
		VTY_MORELINE,
		VTY_PASSFD,
	} status;

	/* vtysh socket/fd passing (for terminal monitor) */
	int pass_fd;

	/* CLI command return value (likely CMD_SUCCESS) when pass_fd != -1 */
	uint8_t pass_fd_status[4];

	/* live logging target / terminal monitor */
	struct zlog_live_cfg live_log;

	/* IAC handling: was the last character received the
	   IAC (interpret-as-command) escape character (and therefore the next
	   character will be the command code)?  Refer to Telnet RFC 854. */
	unsigned char iac;

	/* IAC SB (option subnegotiation) handling */
	unsigned char iac_sb_in_progress;
/* At the moment, we care only about the NAWS (window size) negotiation,
   and that requires just a 5-character buffer (RFC 1073):
     <NAWS char> <16-bit width> <16-bit height> */
#define TELNET_NAWS_SB_LEN 5
	unsigned char sb_buf[TELNET_NAWS_SB_LEN];
	/* How many subnegotiation characters have we received?  We just drop
	   those that do not fit in the buffer. */
	size_t sb_len;

	/* Window width/height. */
	int width;
	int height;

	/* Configure lines. */
	int lines;

	/* Read and write thread. */
	struct event *t_read;
	struct event *t_write;

	/* Timeout seconds and thread. */
	unsigned long v_timeout;
	struct event *t_timeout;

	/* What address is this vty comming from. */
	char address[SU_ADDRSTRLEN];

	/* "frame" output.  This is buffered and will be printed if some
	 * actual output follows, or will be discarded if the frame ends
	 * without any output. */
	size_t frame_pos;
	char frame[1024];

	uint64_t mgmt_session_id; /* FE adapter identifies session w/ this */
	uint64_t mgmt_client_id;  /* FE vty client identifies w/ this ID */
	uint64_t mgmt_req_id;
	/* set when we have sent mgmtd a *REQ command in response to some vty
	 * CLI command and we are waiting on the reply so we can respond to the
	 * vty user. */
	const char *mgmt_req_pending_cmd;
	bool mgmt_locked_candidate_ds;
	bool mgmt_locked_running_ds;
	/* Need to track when we file-lock in vtysh to re-lock on end/conf t
	 * workaround
	 */
	bool vtysh_file_locked;
};

static inline void vty_push_context(struct vty *vty, int node, uint64_t id)
{
	vty->node = node;
	vty->qobj_index = id;
}

/* note: VTY_PUSH_CONTEXT(..., NULL) doesn't work, since it will try to
 * dereference "NULL->qobj_node.nid" */
#define VTY_PUSH_CONTEXT(nodeval, ptr)                                         \
	vty_push_context(vty, nodeval, QOBJ_ID_0SAFE(ptr))
#define VTY_PUSH_CONTEXT_NULL(nodeval) vty_push_context(vty, nodeval, 0ULL)
#define VTY_PUSH_CONTEXT_SUB(nodeval, ptr)                                     \
	do {                                                                   \
		vty->node = nodeval;                                           \
		/* qobj_index stays untouched */                               \
		vty->qobj_index_sub = QOBJ_ID_0SAFE(ptr);                      \
	} while (0)

/* can return NULL if context is invalid! */
#define VTY_GET_CONTEXT(structname)                                            \
	QOBJ_GET_TYPESAFE(vty->qobj_index, structname)
#define VTY_GET_CONTEXT_SUB(structname)                                        \
	QOBJ_GET_TYPESAFE(vty->qobj_index_sub, structname)

/* will return if ptr is NULL. */
#define VTY_CHECK_CONTEXT(ptr)                                                 \
	if (!ptr) {                                                            \
		vty_out(vty,                                                   \
			"Current configuration object was deleted "            \
			"by another process.\n");                              \
		return CMD_WARNING;                                            \
	}

/* struct structname *ptr = <context>;   ptr will never be NULL. */
#define VTY_DECLVAR_CONTEXT(structname, ptr)                                   \
	struct structname *ptr = VTY_GET_CONTEXT(structname);                  \
	VTY_CHECK_CONTEXT(ptr);
#define VTY_DECLVAR_CONTEXT_SUB(structname, ptr)                               \
	struct structname *ptr = VTY_GET_CONTEXT_SUB(structname);              \
	VTY_CHECK_CONTEXT(ptr);
#define VTY_DECLVAR_INSTANCE_CONTEXT(structname, ptr)                          \
	if (vty->qobj_index == 0)                                              \
		return CMD_NOT_MY_INSTANCE;                                    \
	struct structname *ptr = VTY_GET_CONTEXT(structname);                  \
	VTY_CHECK_CONTEXT(ptr);

#define VTY_DECLVAR_CONTEXT_VRF(vrfptr)                                        \
	struct vrf *vrfptr;                                                    \
	if (vty->node == CONFIG_NODE)                                          \
		vrfptr = vrf_lookup_by_id(VRF_DEFAULT);                        \
	else                                                                   \
		vrfptr = VTY_GET_CONTEXT(vrf);                                 \
	VTY_CHECK_CONTEXT(vrfptr);                                             \
	MACRO_REQUIRE_SEMICOLON() /* end */

/* XPath macros. */
#define VTY_PUSH_XPATH(nodeval, value)                                         \
	do {                                                                   \
		if (vty->xpath_index >= VTY_MAXDEPTH) {                        \
			vty_out(vty, "%% Reached maximum CLI depth (%u)\n",    \
				VTY_MAXDEPTH);                                 \
			return CMD_WARNING;                                    \
		}                                                              \
		vty->node = nodeval;                                           \
		strlcpy(vty->xpath[vty->xpath_index], value,                   \
			sizeof(vty->xpath[0]));                                \
		vty->xpath_index++;                                            \
	} while (0)

#define VTY_CURR_XPATH vty->xpath[vty->xpath_index - 1]

#define VTY_CHECK_XPATH                                                        \
	do {                                                                   \
		if (vty->type != VTY_FILE && !vty->private_config &&           \
		    vty->xpath_index > 0 &&                                    \
		    !yang_dnode_exists(vty->candidate_config->dnode,           \
				       VTY_CURR_XPATH)) {                      \
			vty_out(vty,                                           \
				"Current configuration object was deleted "    \
				"by another process.\n\n");                    \
			return CMD_WARNING;                                    \
		}                                                              \
	} while (0)

struct vty_arg {
	const char *name;
	const char *value;
	const char **argv;
	int argc;
};

/* Integrated configuration file. */
#define INTEGRATE_DEFAULT_CONFIG "frr.conf"

/* Default time out value */
#define VTY_TIMEOUT_DEFAULT 600

/* Vty read buffer size. */
#define VTY_READ_BUFSIZ 512

/* Directory separator. */
#ifndef DIRECTORY_SEP
#define DIRECTORY_SEP '/'
#endif /* DIRECTORY_SEP */

#ifndef IS_DIRECTORY_SEP
#define IS_DIRECTORY_SEP(c) ((c) == DIRECTORY_SEP)
#endif

extern struct nb_config *vty_mgmt_candidate_config;
extern bool vty_log_commands;

extern char const *const mgmt_daemons[];
extern uint mgmt_daemons_count;

/* Prototypes. */
extern void vty_init(struct event_loop *m, bool do_command_logging);
extern void vty_init_vtysh(void);
extern void vty_terminate(void);
extern void vty_reset(void);
extern struct vty *vty_new(void);
extern struct vty *vty_stdio(void (*atclose)(int isexit));

/* - vty_frame() output goes to a buffer (for context-begin markers)
 * - vty_out() will first print this buffer, and clear it
 * - vty_endframe() clears the buffer without printing it, and prints an
 *   extra string if the buffer was empty before (for context-end markers)
 */
extern int vty_out(struct vty *, const char *, ...) PRINTFRR(2, 3);
extern void vty_frame(struct vty *, const char *, ...) PRINTFRR(2, 3);
extern void vty_endframe(struct vty *, const char *);
extern bool vty_set_include(struct vty *vty, const char *regexp);
/* returns CMD_SUCCESS so you can do a one-line "return vty_json(...)"
 * NULL check and json_object_free() is included.
 *
 * _no_pretty means do not add a bunch of newlines and dump the output
 * as densely as possible.
 */
extern int vty_json(struct vty *vty, struct json_object *json);
extern int vty_json_no_pretty(struct vty *vty, struct json_object *json);
extern void vty_json_empty(struct vty *vty);
/* post fd to be passed to the vtysh client
 * fd is owned by the VTY code after this and will be closed when done
 */
extern void vty_pass_fd(struct vty *vty, int fd);

extern FILE *vty_open_config(const char *config_file, char *config_default_dir);
extern bool vty_read_config(struct nb_config *config, const char *config_file,
			    char *config_default_dir);
extern void vty_read_file(struct nb_config *config, FILE *confp);
extern void vty_read_file_finish(struct vty *vty, struct nb_config *config);
extern void vty_time_print(struct vty *, int);
extern void vty_serv_start(const char *, unsigned short, const char *);
extern void vty_serv_stop(void);
extern void vty_close(struct vty *);
extern char *vty_get_cwd(void);
extern void vty_update_xpath(const char *oldpath, const char *newpath);
extern int vty_config_enter(struct vty *vty, bool private_config,
			    bool exclusive, bool file_lock);
extern void vty_config_exit(struct vty *);
extern int vty_config_node_exit(struct vty *);
extern int vty_shell(struct vty *);
extern int vty_shell_serv(struct vty *);
extern void vty_hello(struct vty *);

/* ^Z / SIGTSTP handling */
extern void vty_stdio_suspend(void);
extern void vty_stdio_resume(void);
extern void vty_stdio_close(void);

extern void vty_init_mgmt_fe(void);
extern bool vty_mgmt_fe_enabled(void);
extern bool vty_mgmt_should_process_cli_apply_changes(struct vty *vty);

extern bool mgmt_vty_read_configs(void);
extern int vty_mgmt_send_config_data(struct vty *vty, bool implicit_commit);
extern int vty_mgmt_send_commit_config(struct vty *vty, bool validate_only,
				       bool abort);
extern int vty_mgmt_send_get_req(struct vty *vty, bool is_config,
				 Mgmtd__DatastoreId datastore,
				 const char **xpath_list, int num_req);
extern int vty_mgmt_send_lockds_req(struct vty *vty, Mgmtd__DatastoreId ds_id,
				    bool lock, bool scok);
extern void vty_mgmt_resume_response(struct vty *vty, bool success);

static inline bool vty_needs_implicit_commit(struct vty *vty)
{
	return frr_get_cli_mode() == FRR_CLI_CLASSIC && !vty->pending_allowed;
}

#ifdef __cplusplus
}
#endif

#endif /* _ZEBRA_VTY_H */
