// SPDX-License-Identifier: GPL-2.0-or-later
/* User authentication for vtysh.
 * Copyright (C) 2000 Kunihiro Ishiguro
 */

#include <zebra.h>
#include <lib/version.h>

#include <pwd.h>

#ifdef USE_PAM
#include <security/pam_appl.h>
#ifdef HAVE_PAM_MISC_H
#include <security/pam_misc.h>
#endif
#ifdef HAVE_OPENPAM_H
#include <security/openpam.h>
#endif
#endif /* USE_PAM */

#include "memory.h"
#include "linklist.h"
#include "command.h"
#include "vtysh/vtysh_user.h"

/*
 * Compiler is warning about prototypes not being declared.
 * The DEFUNSH and DEFUN macro's are messing with the
 * compiler I believe.  This is just to make it happy.
 */
#ifdef USE_PAM
static int vtysh_pam(const char *);
#endif
int vtysh_auth(void);
void vtysh_user_init(void);

extern struct list *config_top;
extern void config_add_line(struct list *config, const char *line);

#ifdef USE_PAM
static struct pam_conv conv = {PAM_CONV_FUNC, NULL};

static int vtysh_pam(const char *user)
{
	int ret, second_ret;
	pam_handle_t *pamh = NULL;

	/* Start PAM. */
	ret = pam_start(FRR_PAM_NAME, user, &conv, &pamh);

	/* Is user really user? */
	if (ret == PAM_SUCCESS)
		ret = pam_authenticate(pamh, 0);

	if (ret != PAM_SUCCESS)
		fprintf(stderr, "vtysh_pam: Failure to initialize pam: %s(%d)",
			pam_strerror(pamh, ret), ret);

	second_ret = pam_acct_mgmt(pamh, 0);
	if (second_ret != PAM_SUCCESS)
		fprintf(stderr, "%s: Failed in account validation: %s(%d)",
			__func__, pam_strerror(pamh, second_ret), second_ret);

	/* close Linux-PAM */
	second_ret = pam_end(pamh, ret);
	if (second_ret != PAM_SUCCESS) {
		pamh = NULL;
		fprintf(stderr,
			"vtysh_pam: failed to release authenticator: %s(%d)\n",
			pam_strerror(pamh, second_ret), second_ret);
		exit(1);
	}

	return ret == PAM_SUCCESS ? 0 : 1;
}
#endif /* USE_PAM */

struct vtysh_user {
	char *name;
	uint8_t nopassword;
};

struct list *userlist;

static struct vtysh_user *user_new(void)
{
	return XCALLOC(MTYPE_TMP, sizeof(struct vtysh_user));
}

static struct vtysh_user *user_lookup(const char *name)
{
	struct listnode *node, *nnode;
	struct vtysh_user *user;

	for (ALL_LIST_ELEMENTS(userlist, node, nnode, user)) {
		if (strcmp(user->name, name) == 0)
			return user;
	}
	return NULL;
}

void user_config_write(void)
{
	struct listnode *node, *nnode;
	struct vtysh_user *user;
	char line[128];

	for (ALL_LIST_ELEMENTS(userlist, node, nnode, user)) {
		if (user->nopassword) {
			snprintf(line, sizeof(line), "username %s nopassword",
				 user->name);
			config_add_line(config_top, line);
		}
	}
}

static struct vtysh_user *user_get(const char *name)
{
	struct vtysh_user *user;
	user = user_lookup(name);
	if (user)
		return user;

	user = user_new();
	user->name = strdup(name);
	listnode_add(userlist, user);

	return user;
}

DEFUN (vtysh_banner_motd_file,
       vtysh_banner_motd_file_cmd,
       "banner motd file FILE",
       "Set banner\n"
       "Banner for motd\n"
       "Banner from a file\n"
       "Filename\n")
{
	int idx_file = 3;
	return cmd_banner_motd_file(argv[idx_file]->arg);
}

DEFUN (vtysh_banner_motd_line,
       vtysh_banner_motd_line_cmd,
       "banner motd line LINE...",
       "Set banner\n"
       "Banner for motd\n"
       "Banner from an input\n"
       "Text\n")
{
	int idx = 0;
	char *motd;

	argv_find(argv, argc, "LINE", &idx);
	motd = argv_concat(argv, argc, idx);

	cmd_banner_motd_line(motd);
	XFREE(MTYPE_TMP, motd);

	return CMD_SUCCESS;
}

DEFUN (username_nopassword,
       username_nopassword_cmd,
       "username WORD nopassword",
       "\n"
       "\n"
       "\n")
{
	int idx_word = 1;
	struct vtysh_user *user;
	user = user_get(argv[idx_word]->arg);
	user->nopassword = 1;
	return CMD_SUCCESS;
}

int vtysh_auth(void)
{
	struct vtysh_user *user;
	struct passwd *passwd;

	if ((passwd = getpwuid(geteuid())) == NULL) {
		fprintf(stderr, "could not lookup user ID %d\n",
			(int)geteuid());
		exit(1);
	}

	user = user_lookup(passwd->pw_name);
	if (user && user->nopassword)
		/* Pass through */;
	else {
#ifdef USE_PAM
		if (vtysh_pam(passwd->pw_name))
			exit(0);
#endif /* USE_PAM */
	}
	return 0;
}

char *vtysh_get_home(void)
{
	struct passwd *passwd;
	char *homedir;

	if ((homedir = getenv("HOME")) != NULL)
		return homedir;

	/* Fallback if HOME is undefined */
	passwd = getpwuid(getuid());

	return passwd ? passwd->pw_dir : NULL;
}

void vtysh_user_init(void)
{
	userlist = list_new();
	install_element(CONFIG_NODE, &username_nopassword_cmd);
	install_element(CONFIG_NODE, &vtysh_banner_motd_file_cmd);
	install_element(CONFIG_NODE, &vtysh_banner_motd_line_cmd);
}
