/*
 * SPDX-FileCopyrightText: 1990 - 1994, Julianne Frances Haugh
 * SPDX-FileCopyrightText: 1996 - 2000, Marek Michałkiewicz
 * SPDX-FileCopyrightText: 2001 - 2006, Tomasz Kłoczko
 * SPDX-FileCopyrightText: 2007 - 2011, Nicolas François
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */

#include "config.h"

#ident "$Id$"

#include <errno.h>
#include <fcntl.h>
#include <getopt.h>
#include <grp.h>
#include <pwd.h>
#include <signal.h>
#include <stdio.h>
#include <sys/types.h>

#include "agetpass.h"
#include "alloc/malloc.h"
#include "attr.h"
#include "defines.h"
/*@-exitarg@*/
#include "exitcodes.h"
#include "groupio.h"
#include "io/fprintf.h"
#include "nscd.h"
#include "prototypes.h"
#ifdef SHADOWGRP
#include "sgroupio.h"
#endif
#include "shadow/gshadow/sgrp.h"
#include "shadowlog.h"
#include "sssd.h"
#include "string/memset/memzero.h"
#include "string/sprintf/stprintf.h"
#include "string/strcmp/streq.h"
#include "string/strcpy/strtcpy.h"
#include "string/strdup/strdup.h"


struct option_flags {
	bool chroot;
};

/*
 * Global variables
 */
/* The name of this command, as it is invoked */
static const char Prog[] = "gpasswd";

#ifdef SHADOWGRP
/* Indicate if shadow groups are enabled on the system
 * (/etc/gshadow present) */
static bool is_shadowgrp;
#endif

/* Flags set by options */
static bool aflg = false;
static bool Aflg = false;
static bool dflg = false;
static bool Mflg = false;
static bool rflg = false;
static bool Rflg = false;
/* The name of the group that is being affected */
static char *group = NULL;
/* The name of the user being added (-a) or removed (-d) from group */
static char *user = NULL;
/* The new list of members set with -M */
static char *members = NULL;
#ifdef SHADOWGRP
/* The new list of group administrators set with -A */
static char *admins = NULL;
#endif
/* The name of the caller */
static char *myname = NULL;
/* The UID of the caller */
static uid_t bywho;
/* Indicate if gpasswd was called by root */
#define amroot	(0 == bywho)

/* The number of retries for th user to provide and repeat a new password */
#ifndef	RETRIES
#define	RETRIES	3
#endif

/* local function prototypes */
NORETURN static void failure(void);
static void usage (int status);
static void catch_signals (int killed);
static bool is_valid_user_list (const char *users);
static void process_flags (int argc, char **argv, struct option_flags *flags);
static void check_flags (int argc, int opt_index);
static void open_files(const struct option_flags *flags);
static void close_files(const struct option_flags *flags);
#ifdef SHADOWGRP
static void get_group(struct group *gr, struct sgrp *sg, const struct option_flags *flags);
static void check_perms(const struct sgrp *sg);
static void update_group (struct group *gr, struct sgrp *sg);
static void change_passwd (struct group *gr, struct sgrp *sg);
#else
static void get_group(struct group *gr, const struct option_flags *flags);
static void check_perms(void);
static void update_group (struct group *gr);
static void change_passwd (struct group *gr);
#endif
static void log_gpasswd_failure (const char *suffix);
static void log_gpasswd_failure_system (/*@null@*/MAYBE_UNUSED void *_1);
static void log_gpasswd_failure_group (/*@null@*/MAYBE_UNUSED void *_1);
#ifdef SHADOWGRP
static void log_gpasswd_failure_gshadow (/*@null@*/MAYBE_UNUSED void *_1);
#endif
static void log_gpasswd_success (const char *suffix);
static void log_gpasswd_success_system (/*@null@*/MAYBE_UNUSED void *_1);
static void log_gpasswd_success_group (/*@null@*/MAYBE_UNUSED void *_1);

/*
 * usage - display usage message
 */
static void usage (int status)
{
	FILE *usageout = (E_SUCCESS != status) ? stderr : stdout;
	(void) fprintf (usageout,
	                _("Usage: %s [option] GROUP\n"
	                  "\n"
	                  "Options:\n"),
	                Prog);
	(void) fputs (_("  -a, --add USER                add USER to GROUP\n"), usageout);
	(void) fputs (_("  -d, --delete USER             remove USER from GROUP\n"), usageout);
	(void) fputs (_("  -h, --help                    display this help message and exit\n"), usageout);
	(void) fputs (_("  -Q, --root CHROOT_DIR         directory to chroot into\n"), usageout);
	(void) fputs (_("  -r, --remove-password         remove the GROUP's password\n"), usageout);
	(void) fputs (_("  -R, --restrict                restrict access to GROUP to its members\n"), usageout);
	(void) fputs (_("  -M, --members USER,...        set the list of members of GROUP\n"), usageout);
#ifdef SHADOWGRP
	(void) fputs (_("  -A, --administrators ADMIN,...\n"
	                "                                set the list of administrators for GROUP\n"), usageout);
	(void) fputs (_("Except for the -A and -M options, the options cannot be combined.\n"), usageout);
#else
	(void) fputs (_("The options cannot be combined.\n"), usageout);
#endif
	exit (status);
}

/*
 * catch_signals - set or reset termio modes.
 *
 *	catch_signals() is called before processing begins. signal() is then
 *	called with catch_signals() as the signal handler. If signal later
 *	calls catch_signals() with a signal number, the terminal modes are
 *	then reset.
 */
static void catch_signals (int killed)
{
	static TERMIO sgtty;

	if (0 != killed) {
		STTY (0, &sgtty);
	} else {
		GTTY (0, &sgtty);
	}

	if (0 != killed) {
		(void) write (STDOUT_FILENO, "\n", 1);
		_exit (killed);
	}
}

/*
 * is_valid_user_list - check a comma-separated list of user names for validity
 *
 *	is_valid_user_list scans a comma-separated list of user names and
 *	checks that each listed name exists is the user database.
 *
 *	It returns true if the list of users is valid.
 */
static bool is_valid_user_list (const char *users)
{
	bool is_valid = true;
	char  *dup, *tmpusers;

	tmpusers = dup = xstrdup(users);

	while (NULL != tmpusers && !streq(tmpusers, "")) {
		const char  *u;

		u = strsep(&tmpusers, ",");

		/*
		 * This user must exist.
		 */

		/* local, no need for xgetpwnam */
		if (getpwnam(u) == NULL) {
			eprintf(_("%s: user '%s' does not exist\n"), Prog, u);
			is_valid = false;
		}
	}

	free(dup);

	return is_valid;
}

static void failure(void)
{
	eprintf(_("%s: Permission denied.\n"), Prog);
	log_gpasswd_failure(": Permission denied");
	exit(E_NOPERM);
}

/*
 * process_flags - process the command line options and arguments
 */
static void process_flags (int argc, char **argv, struct option_flags *flags)
{
	int c;
	static struct option long_options[] = {
		{"add",             required_argument, NULL, 'a'},
		{"administrators",  required_argument, NULL, 'A'},
		{"delete",          required_argument, NULL, 'd'},
		{"help",            no_argument,       NULL, 'h'},
		{"members",         required_argument, NULL, 'M'},
		{"root",            required_argument, NULL, 'Q'},
		{"remove-password", no_argument,       NULL, 'r'},
		{"restrict",        no_argument,       NULL, 'R'},
		{NULL, 0, NULL, '\0'}
		};

	while ((c = getopt_long (argc, argv, "a:A:d:ghM:Q:rR",
	                         long_options, NULL)) != -1) {
		switch (c) {
		case 'a':	/* add a user */
			aflg = true;
			user = optarg;
			/* local, no need for xgetpwnam */
			if (getpwnam (user) == NULL) {
				eprintf(_("%s: user '%s' does not exist\n"),
				         Prog, user);
				exit (E_BAD_ARG);
			}
			break;
#ifdef SHADOWGRP
		case 'A':	/* set the list of administrators */
			if (!is_shadowgrp) {
				eprintf(_("%s: shadow group passwords required for -A\n"),
				         Prog);
				exit (E_GSHADOW_NOTFOUND);
			}
			admins = optarg;
			if (!is_valid_user_list (admins)) {
				exit (E_BAD_ARG);
			}
			Aflg = true;
			break;
#endif				/* SHADOWGRP */
		case 'd':	/* delete a user */
			dflg = true;
			user = optarg;
			break;
		case 'g':	/* no-op from normal password */
			break;
		case 'h':
			usage (E_SUCCESS);
			break;
		case 'M':	/* set the list of members */
			members = optarg;
			if (!is_valid_user_list (members)) {
				exit (E_BAD_ARG);
			}
			Mflg = true;
			break;
		case 'Q':	/* no-op, handled in process_root_flag () */
			flags->chroot = true;
			break;
		case 'r':	/* remove group password */
			rflg = true;
			break;
		case 'R':	/* restrict group password */
			Rflg = true;
			break;
		default:
			usage (E_USAGE);
		}
	}

	/* Get the name of the group that is being affected. */
	group = argv[optind];

	check_flags (argc, optind);
}

/*
 * check_flags - check the validity of options
 */
static void check_flags (int argc, int opt_index)
{
	int exclusive = 0;
	/*
	 * Make sure exclusive flags are exclusive
	 */
	if (aflg) {
		exclusive++;
	}
	if (dflg) {
		exclusive++;
	}
	if (rflg) {
		exclusive++;
	}
	if (Rflg) {
		exclusive++;
	}
	if (Aflg || Mflg) {
		exclusive++;
	}
	if (exclusive > 1) {
		usage (E_USAGE);
	}

	/*
	 * Make sure one (and only one) group was provided
	 */
	if ((argc != (opt_index+1)) || (NULL == group)) {
		usage (E_USAGE);
	}
}

/*
 * open_files - lock and open the group databases
 *
 *	It will call exit in case of error.
 */
static void open_files(const struct option_flags *flags)
{
	static bool process_selinux;

	process_selinux = !flags->chroot;

	if (gr_lock () == 0) {
		eprintf(_("%s: cannot lock %s; try again later.\n"),
		         Prog, gr_dbname ());
		exit (E_NOPERM);
	}
	add_cleanup (cleanup_unlock_group, &process_selinux);

#ifdef SHADOWGRP
	if (is_shadowgrp) {
		if (sgr_lock () == 0) {
			eprintf(_("%s: cannot lock %s; try again later.\n"),
			         Prog, sgr_dbname ());
			exit (E_NOPERM);
		}
		add_cleanup (cleanup_unlock_gshadow, &process_selinux);
	}
#endif				/* SHADOWGRP */

	add_cleanup (log_gpasswd_failure_system, NULL);

	if (gr_open (O_CREAT | O_RDWR) == 0) {
		eprintf(_("%s: cannot open %s\n"), Prog, gr_dbname());
		SYSLOG(LOG_WARN, "cannot open %s", gr_dbname());
		exit (E_NOPERM);
	}

#ifdef SHADOWGRP
	if (is_shadowgrp) {
		if (sgr_open (O_CREAT | O_RDWR) == 0) {
			eprintf(_("%s: cannot open %s\n"), Prog, sgr_dbname());
			SYSLOG(LOG_WARN, "cannot open %s", sgr_dbname());
			exit (E_NOPERM);
		}
		add_cleanup (log_gpasswd_failure_gshadow, NULL);
	}
#endif				/* SHADOWGRP */

	add_cleanup (log_gpasswd_failure_group, NULL);
	del_cleanup (log_gpasswd_failure_system);
}

static void log_gpasswd_failure (const char *suffix)
{
	if (aflg) {
		SYSLOG(LOG_ERR, "%s failed to add user %s to group %s%s",
		       myname, user, group, suffix);
#ifdef WITH_AUDIT
		audit_logger_with_group (AUDIT_USER_MGMT,
		              "add-user-to-group",
		              user, AUDIT_NO_ID, "grp", group,
		              SHADOW_AUDIT_FAILURE);
#endif
	} else if (dflg) {
		SYSLOG(LOG_ERR, "%s failed to remove user %s from group %s%s",
		       myname, user, group, suffix);
#ifdef WITH_AUDIT
		audit_logger_with_group (AUDIT_USER_MGMT,
		              "delete-user-from-group",
		              user, AUDIT_NO_ID, "grp", group,
		              SHADOW_AUDIT_FAILURE);
#endif
	} else if (rflg) {
		SYSLOG(LOG_ERR, "%s failed to remove password of group %s%s",
		       myname, group, suffix);
#ifdef WITH_AUDIT
		audit_logger_with_group (AUDIT_GRP_CHAUTHTOK,
		              "delete-group-password",
		              myname, AUDIT_NO_ID, "grp", group,
		              SHADOW_AUDIT_FAILURE);
#endif
	} else if (Rflg) {
		SYSLOG(LOG_ERR, "%s failed to restrict access to group %s%s",
		       myname, group, suffix);
#ifdef WITH_AUDIT
		audit_logger_with_group (AUDIT_GRP_MGMT,
		              "restrict-group",
		              myname, AUDIT_NO_ID, "grp", group,
		              SHADOW_AUDIT_FAILURE);
#endif
	} else if (Aflg || Mflg) {
#ifdef SHADOWGRP
		if (Aflg) {
			SYSLOG(LOG_ERR,
			       "%s failed to set the administrators of group %s to %s%s",
			       myname, group, admins, suffix);
#ifdef WITH_AUDIT
			audit_logger_with_group (AUDIT_GRP_MGMT,
			              "set-admins-of-group",
			              admins, AUDIT_NO_ID, "grp", group,
			              SHADOW_AUDIT_FAILURE);
#endif
		}
#endif				/* SHADOWGRP */
		if (Mflg) {
			SYSLOG(LOG_ERR,
			       "%s failed to set the members of group %s to %s%s",
			       myname, group, members, suffix);
#ifdef WITH_AUDIT
			audit_logger_with_group (AUDIT_USER_MGMT,
			              "add-users-to-group",
			              members, AUDIT_NO_ID, "grp", group,
			              SHADOW_AUDIT_FAILURE);
#endif
		}
	} else {
		SYSLOG(LOG_ERR, "%s failed to change password of group %s%s",
		       myname, group, suffix);
#ifdef WITH_AUDIT
		audit_logger_with_group (AUDIT_GRP_CHAUTHTOK,
		              "change-password",
		              myname, AUDIT_NO_ID, "grp", group,
		              SHADOW_AUDIT_FAILURE);
#endif
	}
}

static void
log_gpasswd_failure_system(MAYBE_UNUSED void *_1)
{
	log_gpasswd_failure ("");
}

static void
log_gpasswd_failure_group(MAYBE_UNUSED void *_1)
{
	char  buf[1024];

	stprintf_a(buf, " in %s", gr_dbname());
	log_gpasswd_failure (buf);
}

#ifdef SHADOWGRP
static void
log_gpasswd_failure_gshadow(MAYBE_UNUSED void *_1)
{
	char  buf[1024];

	stprintf_a(buf, " in %s", sgr_dbname());
	log_gpasswd_failure (buf);
}
#endif				/* SHADOWGRP */

static void log_gpasswd_success (const char *suffix)
{
#ifdef WITH_AUDIT
	char  buf[1024];
#endif

	if (aflg) {
		SYSLOG(LOG_INFO, "user %s added by %s to group %s%s",
		       user, myname, group, suffix);
#ifdef WITH_AUDIT
		audit_logger_with_group (AUDIT_USER_MGMT,
		              "add-user-to-group",
		              user, AUDIT_NO_ID, "grp", group,
		              SHADOW_AUDIT_SUCCESS);
#endif
	} else if (dflg) {
		SYSLOG(LOG_INFO, "user %s removed by %s from group %s%s",
		       user, myname, group, suffix);
#ifdef WITH_AUDIT
		audit_logger_with_group (AUDIT_USER_MGMT,
		              "delete-user-from-group",
		              user, AUDIT_NO_ID, "grp", group,
		              SHADOW_AUDIT_SUCCESS);
#endif
	} else if (rflg) {
		SYSLOG(LOG_INFO, "password of group %s removed by %s%s",
		       group, myname, suffix);
#ifdef WITH_AUDIT
		stprintf_a(buf, "password of group %s removed by %s%s",
		         group, myname, suffix);
		audit_logger_with_group (AUDIT_GRP_CHAUTHTOK,
		              "delete-group-password",
		              myname, AUDIT_NO_ID, "grp", group,
		              SHADOW_AUDIT_SUCCESS);
#endif
	} else if (Rflg) {
		SYSLOG(LOG_INFO, "access to group %s restricted by %s%s",
		       group, myname, suffix);
#ifdef WITH_AUDIT
		stprintf_a(buf, "access to group %s restricted by %s%s",
		         group, myname, suffix);
		audit_logger_with_group (AUDIT_GRP_MGMT,
		              "restrict-group",
		              myname, AUDIT_NO_ID, "grp", group,
		              SHADOW_AUDIT_SUCCESS);
#endif
	} else if (Aflg || Mflg) {
#ifdef SHADOWGRP
		if (Aflg) {
			SYSLOG(LOG_INFO, "administrators of group %s set by %s to %s%s",
			       group, myname, admins, suffix);
#ifdef WITH_AUDIT
			audit_logger_with_group (AUDIT_GRP_MGMT,
			              "set-admins-of-group",
			              admins, AUDIT_NO_ID, "grp", group,
			              SHADOW_AUDIT_SUCCESS);
#endif
		}
#endif				/* SHADOWGRP */
		if (Mflg) {
			SYSLOG(LOG_INFO, "members of group %s set by %s to %s%s",
			       group, myname, members, suffix);
#ifdef WITH_AUDIT
			audit_logger_with_group (AUDIT_USER_MGMT,
			              "add-users-to-group",
			              members, AUDIT_NO_ID, "grp", group,
			              SHADOW_AUDIT_SUCCESS);
#endif
		}
	} else {
		SYSLOG(LOG_INFO, "password of group %s changed by %s%s",
		       group, myname, suffix);
#ifdef WITH_AUDIT
		audit_logger_with_group (AUDIT_GRP_CHAUTHTOK,
		              "change-password",
		              myname, AUDIT_NO_ID, "grp", group,
		              SHADOW_AUDIT_SUCCESS);
#endif
	}
}

static void
log_gpasswd_success_system(MAYBE_UNUSED void *_1)
{
	log_gpasswd_success ("");
}

static void
log_gpasswd_success_group(MAYBE_UNUSED void *_1)
{
	char  buf[1024];

	stprintf_a(buf, " in %s", gr_dbname());
	log_gpasswd_success (buf);
}

/*
 * close_files - close and unlock the group databases
 *
 *	This cause any changes in the databases to be committed.
 *
 *	It will call exit in case of error.
 */
static void close_files(const struct option_flags *flags)
{
	bool process_selinux;

	process_selinux = !flags->chroot;

	if (gr_close (process_selinux) == 0) {
		eprintf(_("%s: failure while writing changes to %s\n"),
		         Prog, gr_dbname ());
		exit (E_NOPERM);
	}
	add_cleanup (log_gpasswd_success_group, NULL);
	del_cleanup (log_gpasswd_failure_group);

	cleanup_unlock_group (&process_selinux);
	del_cleanup (cleanup_unlock_group);

#ifdef SHADOWGRP
	if (is_shadowgrp) {
		if (sgr_close (process_selinux) == 0) {
			eprintf(_("%s: failure while writing changes to %s\n"),
			         Prog, sgr_dbname ());
			exit (E_NOPERM);
		}
		del_cleanup (log_gpasswd_failure_gshadow);

		cleanup_unlock_gshadow (&process_selinux);
		del_cleanup (cleanup_unlock_gshadow);
	}
#endif				/* SHADOWGRP */

	log_gpasswd_success_system (NULL);
	del_cleanup (log_gpasswd_success_group);
}

/*
 * check_perms - check if the user is allowed to change the password of
 *               the specified group.
 *
 *	It only returns if the user is allowed.
 */
#ifdef SHADOWGRP
static void check_perms (const struct sgrp *sg)
#else
static void check_perms (void)
#endif
{
	/*
	 * Only root can use the -M and -A options.
	 */
	if (!amroot && (Aflg || Mflg)) {
		failure ();
	}

#ifdef SHADOWGRP
	if (is_shadowgrp) {
		/*
		 * The policy here for changing a group is that
		 * 1) you must be root or
		 * 2) you must be listed as an administrative member.
		 * Administrative members can do anything to a group that
		 * the root user can.
		 */
		if (!amroot && !is_on_list (sg->sg_adm, myname)) {
			failure ();
		}
	} else
#endif				/* SHADOWGRP */
	if (!amroot)
		failure();
}

/*
 * update_group - Update the group information in the databases
 */
#ifdef SHADOWGRP
static void update_group (struct group *gr, struct sgrp *sg)
#else
static void update_group (struct group *gr)
#endif
{
	if (gr_update (gr) == 0) {
		eprintf(_("%s: failed to prepare the new %s entry '%s'\n"),
		         Prog, gr_dbname (), gr->gr_name);
		exit (1);
	}
#ifdef SHADOWGRP
	if (is_shadowgrp && (sgr_update (sg) == 0)) {
		eprintf(_("%s: failed to prepare the new %s entry '%s'\n"),
		         Prog, sgr_dbname (), sg->sg_namp);
		exit (1);
	}
#endif				/* SHADOWGRP */
}

/*
 * get_group - get the current information for the group
 *
 *	The information are copied in group structure(s) so that they can be
 *	modified later.
 *
 *	Note: if !is_shadowgrp, *sg will not be initialized.
 */
#ifdef SHADOWGRP
static void get_group(struct group *gr, struct sgrp *sg, const struct option_flags *flags)
#else
static void get_group(struct group *gr, const struct option_flags *flags)
#endif
{
	struct group const*tmpgr = NULL;
#ifdef SHADOWGRP
	struct sgrp const*tmpsg = NULL;
#endif
	bool process_selinux;

	process_selinux = !flags->chroot;

	if (gr_open (O_RDONLY) == 0) {
		eprintf(_("%s: cannot open %s\n"), Prog, gr_dbname());
		SYSLOG(LOG_WARN, "cannot open %s", gr_dbname());
		exit (E_NOPERM);
	}

	tmpgr = gr_locate (group);
	if (NULL == tmpgr) {
		eprintf(_("%s: group '%s' does not exist in %s\n"),
		         Prog, group, gr_dbname ());
		exit (E_BAD_ARG);
	}

	*gr = *tmpgr;
	gr->gr_name = xstrdup (tmpgr->gr_name);
	gr->gr_passwd = xstrdup (tmpgr->gr_passwd);
	gr->gr_mem = dup_list (tmpgr->gr_mem);

	if (gr_close (process_selinux) == 0) {
		eprintf(_("%s: failure while closing read-only %s\n"),
		         Prog, gr_dbname ());
		SYSLOG(LOG_ERR, "failure while closing read-only %s", gr_dbname());
		exit (E_NOPERM);
	}

#ifdef SHADOWGRP
	if (is_shadowgrp) {
		if (sgr_open (O_RDONLY) == 0) {
			eprintf(_("%s: cannot open %s\n"), Prog, sgr_dbname());
			SYSLOG(LOG_WARN, "cannot open %s", sgr_dbname());
			exit (E_NOPERM);
		}
		tmpsg = sgr_locate (group);
		if (NULL != tmpsg) {
			*sg = *tmpsg;
			sg->sg_namp = xstrdup (tmpsg->sg_namp);
			sg->sg_passwd = xstrdup (tmpsg->sg_passwd);

			sg->sg_mem = dup_list (tmpsg->sg_mem);
			sg->sg_adm = dup_list (tmpsg->sg_adm);
		} else {
			sg->sg_namp = xstrdup (group);
			sg->sg_passwd = gr->gr_passwd;
			gr->gr_passwd = SHADOW_PASSWD_STRING;	/* XXX warning: const */

			sg->sg_mem = dup_list (gr->gr_mem);

			sg->sg_adm = xmalloc_T(1, char *);
			sg->sg_adm[0] = NULL;

		}
		if (sgr_close (process_selinux) == 0) {
			eprintf(_("%s: failure while closing read-only %s\n"),
			         Prog, sgr_dbname ());
			SYSLOG(LOG_ERR, "failure while closing read-only %s",
			       sgr_dbname());
			exit (E_NOPERM);
		}
	}
#endif				/* SHADOWGRP */
}

/*
 * change_passwd - change the group's password
 *
 *	Get the new password from the user and update the password in the
 *	group's structure.
 *
 *	It will call exit in case of error.
 */
#ifdef SHADOWGRP
static void change_passwd (struct group *gr, struct sgrp *sg)
#else
static void change_passwd (struct group *gr)
#endif
{
	char *cp;
	static char pass[PASS_MAX + 1];
	int retries;
	const char *salt;

	/*
	 * A new password is to be entered and it must be encrypted, etc.
	 * The password will be prompted for twice, and both entries must be
	 * identical. There is no need to validate the old password since
	 * the invoker is either the group owner, or root.
	 */
	printf (_("Changing the password for group %s\n"), group);

	for (retries = 0; retries < RETRIES; retries++) {
		cp = agetpass (_("New Password: "));
		if (NULL == cp) {
			exit (1);
		}

		strtcpy_a(pass, cp);
		erase_pass (cp);
		cp = agetpass (_("Re-enter new password: "));
		if (NULL == cp) {
			memzero_a(pass);
			exit (1);
		}

		if (streq(pass, cp)) {
			erase_pass (cp);
			break;
		}

		erase_pass (cp);
		memzero_a(pass);

		if (retries + 1 < RETRIES) {
			puts (_("They don't match; try again"));
		}
	}

	if (retries == RETRIES) {
		eprintf(_("%s: Try again later\n"), Prog);
		exit (1);
	}

	salt = crypt_make_salt (NULL, NULL);
	cp = pw_encrypt (pass, salt);
	memzero_a(pass);
	if (NULL == cp) {
		eprinte(_("%s: failed to crypt password with salt '%s'"), Prog, salt);
		exit (1);
	}
#ifdef SHADOWGRP
	if (is_shadowgrp) {
		gr->gr_passwd = SHADOW_PASSWD_STRING;
		sg->sg_passwd = cp;
	} else
#endif
	{
		gr->gr_passwd = cp;
	}
}

/*
 * gpasswd - administer the /etc/group file
 */
int main (int argc, char **argv)
{
	struct group grent;
#ifdef SHADOWGRP
	struct sgrp sgent;
#endif
	struct passwd *pw = NULL;
	struct option_flags  flags = {.chroot = false};

	sanitize_env ();
	check_fds ();

	(void) setlocale (LC_ALL, "");
	(void) bindtextdomain (PACKAGE, LOCALEDIR);
	(void) textdomain (PACKAGE);

	/*
	 * Make a note of whether or not this command was invoked by root.
	 * This will be used to bypass certain checks later on. Also, set
	 * the real user ID to match the effective user ID. This will
	 * prevent the invoker from issuing signals which would interfere
	 * with this command.
	 */
	bywho = getuid ();
	log_set_progname(Prog);
	log_set_logfd(stderr);

	OPENLOG (Prog);
#ifdef WITH_AUDIT
	audit_help_open ();
#endif

	setbuf (stdout, NULL);
	setbuf (stderr, NULL);

	process_root_flag ("-Q", argc, argv);

#ifdef SHADOWGRP
	is_shadowgrp = sgr_file_present ();
#endif

	/*
	 * Determine the name of the user that invoked this command. This
	 * is really hit or miss because there are so many ways that command
	 * can be executed and so many ways to trip up the routines that
	 * report the user name.
	 */
	pw = get_my_pwent ();
	if (NULL == pw) {
		eprintf(_("%s: Cannot determine your user name.\n"), Prog);
		SYSLOG(LOG_WARN,
		       "Cannot determine the user name of the caller (UID %lu)",
		       (unsigned long) getuid());
		exit (E_NOPERM);
	}
	myname = xstrdup (pw->pw_name);

	/*
	 * Register an exit function to warn for any inconsistency that we
	 * could create.
	 */
	if (atexit (do_cleanups) != 0) {
		eprintf("%s: cannot set exit function\n", Prog);
		exit (1);
	}

	/* Parse the options */
	process_flags (argc, argv, &flags);

	/*
	 * Replicate the group so it can be modified later on.
	 */
#ifdef SHADOWGRP
	get_group (&grent, &sgent, &flags);
#else
	get_group (&grent, &flags);
#endif

	/*
	 * Check if the user is allowed to change the password of this group.
	 */
#ifdef SHADOWGRP
	check_perms(&sgent);
#else
	check_perms();
#endif

	/*
	 * Removing a password is straight forward. Just set the password
	 * field to a "".
	 */
	if (rflg) {
#ifdef SHADOWGRP
		if (is_shadowgrp) {
			grent.gr_passwd = SHADOW_PASSWD_STRING;	/* XXX warning: const */
			sgent.sg_passwd = "";	/* XXX warning: const */
		} else
#endif				/* SHADOWGRP */
		{
			grent.gr_passwd = "";	/* XXX warning: const */
		}
		goto output;
	} else if (Rflg) {
		/*
		 * Same thing for restricting the group. Set the password
		 * field to "!".
		 */
#ifdef SHADOWGRP
		if (is_shadowgrp) {
			grent.gr_passwd = SHADOW_PASSWD_STRING;	/* XXX warning: const */
			sgent.sg_passwd = "!";	/* XXX warning: const */
		} else
#endif				/* SHADOWGRP */
		{
			grent.gr_passwd = "!";	/* XXX warning: const */
		}
		goto output;
	}

	/*
	 * Adding a member to a member list is pretty straightforward as
	 * well. Call the appropriate routine and split.
	 */
	if (aflg) {
		printf (_("Adding user %s to group %s\n"), user, group);
		grent.gr_mem = add_list (grent.gr_mem, user);
#ifdef SHADOWGRP
		if (is_shadowgrp) {
			sgent.sg_mem = add_list (sgent.sg_mem, user);
		}
#endif
		goto output;
	}

	/*
	 * Removing a member from the member list is the same deal as adding
	 * one, except the routine is different.
	 */
	if (dflg) {
		bool removed = false;

		printf (_("Removing user %s from group %s\n"), user, group);

		if (is_on_list (grent.gr_mem, user)) {
			removed = true;
			grent.gr_mem = del_list (grent.gr_mem, user);
		}
#ifdef SHADOWGRP
		if (is_shadowgrp) {
			if (is_on_list (sgent.sg_mem, user)) {
				removed = true;
				sgent.sg_mem = del_list (sgent.sg_mem, user);
			}
		}
#endif
		if (!removed) {
			eprintf(_("%s: user '%s' is not a member of '%s'\n"),
			         Prog, user, group);
			exit (E_BAD_ARG);
		}
		goto output;
	}
#ifdef SHADOWGRP
	/*
	 * Replacing the entire list of administrators is simple. Check the
	 * list to make sure everyone is a real user. Then slap the new list
	 * in place.
	 */
	if (Aflg) {
		sgent.sg_adm = comma_to_list (admins);
		if (!Mflg) {
			goto output;
		}
	}
#endif				/* SHADOWGRP */

	/*
	 * Replacing the entire list of members is simple. Check the list to
	 * make sure everyone is a real user. Then slap the new list in
	 * place.
	 */
	if (Mflg) {
#ifdef SHADOWGRP
		sgent.sg_mem = comma_to_list (members);
#endif
		grent.gr_mem = comma_to_list (members);
		goto output;
	}

	/*
	 * If the password is being changed, the input and output must both
	 * be a tty. The typical keyboard signals are caught so the termio
	 * modes can be restored.
	 */
	if ((isatty (0) == 0) || (isatty (1) == 0)) {
		eprintf(_("%s: Not a tty\n"), Prog);
		exit (E_NOPERM);
	}

	catch_signals (0);	/* save tty modes */

	(void) signal (SIGHUP, catch_signals);
	(void) signal (SIGINT, catch_signals);
	(void) signal (SIGQUIT, catch_signals);
	(void) signal (SIGTERM, catch_signals);
	(void) signal (SIGTSTP, catch_signals);

	/* Prompt for the new password */
#ifdef SHADOWGRP
	change_passwd (&grent, &sgent);
#else
	change_passwd (&grent);
#endif

	/*
	 * This is the common arrival point to output the new group file.
	 * The freshly crafted entry is in allocated space. The group file
	 * will be locked and opened for writing. The new entry will be
	 * output, etc.
	 */
      output:
	if (setuid (0) != 0) {
		fputs (_("Cannot change ID to root.\n"), stderr);
		SYSLOG(LOG_ERR, "can't setuid(0)");
		closelog ();
		exit (E_NOPERM);
	}
	pwd_init ();

	open_files (&flags);

#ifdef SHADOWGRP
	update_group (&grent, &sgent);
#else
	update_group (&grent);
#endif

	close_files (&flags);

	nscd_flush_cache ("group");
	sssd_flush_cache (SSSD_DB_GROUP);

#ifdef SHADOWGRP
	if (is_shadowgrp) {
		free(sgent.sg_adm);
		free(sgent.sg_mem);
	}
#endif
	free(grent.gr_mem);
	exit (E_SUCCESS);
}

